UNPKG

@studiometa/js-toolkit

Version:

A set of useful little bits of JavaScript to boost your project! 🚀

228 lines (227 loc) • 5.74 kB
import { AbstractService } from "./AbstractService.js"; import { useRaf } from "./RafService.js"; import { isDefined } from "../utils/is.js"; import { inertiaFinalValue } from "../utils/math/index.js"; import { PASSIVE_EVENT_OPTIONS, CAPTURE_EVENT_OPTIONS } from "./utils.js"; let count = 0; class DragService extends AbstractService { static MODES = { START: "start", DRAG: "drag", DROP: "drop", INERTIA: "inertia", STOP: "stop" }; static config = [ [ (instance) => instance.props.target, [ ["dragstart", CAPTURE_EVENT_OPTIONS], ["click", CAPTURE_EVENT_OPTIONS], ["pointerdown", PASSIVE_EVENT_OPTIONS] ] ], [ () => window, [ ["pointerup", PASSIVE_EVENT_OPTIONS], ["touchend", PASSIVE_EVENT_OPTIONS] ] ] ]; id; dampFactor = 0.85; dragTreshold = 10; previousEvent = null; props = { target: null, mode: void 0, MODES: DragService.MODES, isGrabbing: false, hasInertia: false, x: 0, y: 0, delta: { x: 0, y: 0 }, origin: { x: 0, y: 0 }, distance: { x: 0, y: 0 }, final: { x: 0, y: 0 } }; /** * Test if we should allow click on links and buttons. */ get shouldPreventClick() { return Math.abs(this.props.distance.x) > this.dragTreshold || Math.abs(this.props.distance.y) > this.dragTreshold; } constructor(target, { dampFactor = 0.85, dragTreshold = 10 } = {}) { super(); count += 1; this.id = `drag-${count}`; this.dampFactor = dampFactor; this.dragTreshold = dragTreshold; this.props.target = target; } /** * Get the client value for the given axis. */ getEventPosition(event) { const eventOrTouch = isDefined(event.touches) ? event.touches[0] : event; return { x: eventOrTouch.clientX, y: eventOrTouch.clientY }; } /** * Start the drag. * * @param {number} x The initial horizontal position. * @param {number} y The initial vertical position. */ start(x, y) { const { props } = this; if (props.isGrabbing) { return; } props.x = props.origin.x = props.final.x = x; props.y = props.origin.y = props.final.y = y; props.delta.x = props.distance.x = 0; props.delta.y = props.distance.y = 0; props.mode = props.MODES.START; props.isGrabbing = true; this.trigger(props); document.addEventListener("touchmove", this, PASSIVE_EVENT_OPTIONS); document.addEventListener("mousemove", this, PASSIVE_EVENT_OPTIONS); } /** * Stop the drag, or drop. */ drop() { const { props, dampFactor, id } = this; if (!props.isGrabbing) { return; } document.removeEventListener("touchmove", this); document.removeEventListener("mousemove", this); props.isGrabbing = false; props.mode = props.MODES.DROP; props.hasInertia = true; props.final.x = inertiaFinalValue(props.x, props.delta.x, dampFactor); props.final.y = inertiaFinalValue(props.y, props.delta.y, dampFactor); this.previousEvent = null; this.trigger(props); setTimeout(() => { const raf = useRaf(); raf.remove(id); raf.add(id, () => this.rafHandler()); }); } /** * Stop the drag. */ stop() { const { props, id } = this; useRaf().remove(id); props.isGrabbing = false; props.hasInertia = false; props.mode = props.MODES.STOP; this.trigger(props); } /** * Raf service handler. */ rafHandler() { const { props, dampFactor } = this; if (!props.isGrabbing) { props.x += props.delta.x; props.y += props.delta.y; props.distance.x = props.x - props.origin.x; props.distance.y = props.y - props.origin.y; props.delta.x *= dampFactor; props.delta.y *= dampFactor; props.mode = props.MODES.INERTIA; this.trigger(props); if (Math.abs(props.delta.x) < 0.1 && Math.abs(props.delta.y) < 0.1) { this.stop(); } } } /** * Pointer service handler. */ drag(event) { const { props } = this; if (props.isGrabbing) { const position = this.getEventPosition(event); props.x = position.x; props.y = position.y; if (this.previousEvent) { const previousPosition = this.getEventPosition(this.previousEvent); props.delta.x = position.x - previousPosition.x; props.delta.y = position.y - previousPosition.y; } props.final.x = position.x; props.final.y = position.y; props.distance.x = props.x - props.origin.x; props.distance.y = props.y - props.origin.y; props.mode = props.MODES.DRAG; this.trigger(props); this.previousEvent = event; } } /** * Handle any event. */ handleEvent(event) { switch (event.type) { case "dragstart": event.preventDefault(); break; case "click": if (this.shouldPreventClick) { event.stopImmediatePropagation(); event.stopPropagation(); event.preventDefault(); } break; case "pointerup": case "touchend": this.drop(); break; case "touchmove": case "mousemove": if (event.buttons === 1 || event.touches?.length === 1) { this.drag(event); } else { this.drop(); } break; default: if (event.button === 0) { this.start(event.x, event.y); } } } } function useDrag(target, options) { return DragService.getInstance( [target, JSON.stringify(options)], target, options ); } export { DragService, useDrag }; //# sourceMappingURL=DragService.js.map