UNPKG

@figliolia/drag-detector

Version:

Mouse and Touch driven drag detection for DOM elements

165 lines (164 loc) 4.72 kB
/** * DragDetector * * Given an x/y Threshold, detects mouse and touch driven * drags on the target element. An element is targeted by * calling: * * ```typescript * const node = document.getElementById("myElement"); * const detector = new DragDetector({ * callback: ({ xDelta, rect, node }) => { * const translate = parseInt(node.style.translate.slice(0, -2)); * const next = Math.max(0, Math.min(translate + xDelta, rect.width)); * node.style.translate = next; * } * }); * detector.register(node); * node.addEventListener("mousedown", detector.onMouseDown); * node.addEventListener("touchstart", detector.onMouseDown); * ``` */ export class DragDetector { node; active = false; startX = 0; startY = 0; xDelta = 0; yDelta = 0; currentX = 0; currentY = 0; lastRect; options; constructor(options) { this.options = DragDetector.mergeDefaultOptions(options); } static defaultOptions = { xThreshold: Infinity, yThreshold: Infinity, }; /** * register * * Registers a DOM node to target for the `DragDetector` * instance */ register = (node) => { this.node = node; }; /** * setOptions * * Allows users to update options on the fly */ setOptions(options) { this.options = DragDetector.mergeDefaultOptions(options); } /** * destroy * * Cleans up the `DragDetector` instance and event listeners */ destroy() { this.active = false; this.lastRect = undefined; document.removeEventListener("mousemove", this.onMouseMove); document.removeEventListener("mouseup", this.onMouseUp); document.removeEventListener("touchmove", this.onMouseMove); document.removeEventListener("touchend", this.onMouseUp); } onMouseDown = (e) => { this.yDelta = 0; this.xDelta = 0; this.currentX = 0; this.currentY = 0; if (!this.node) { return; } const [x, y] = this.mouseCoordinates(this.node, e); const { xThreshold, yThreshold } = this.options; if (x <= xThreshold && y <= yThreshold) { this.startX = x; this.startY = y; this.currentX = x; this.currentY = y; this.active = true; this.listen(); } }; onMouseMove = (e) => { if (!this.active || !this.node) { return; } const [x, y, rect] = this.mouseCoordinates(this.node, e); this.lastRect = rect; this.xDelta = x - this.currentX; this.yDelta = y - this.currentY; this.options.callback({ x, y, rect, node: this.node, xDelta: this.xDelta, yDelta: this.yDelta, xDistance: x - this.startX, yDistance: y - this.startY, }); this.currentX = x; this.currentY = y; }; onMouseUp = () => { this.options.callback({ exit: true, node: this.node, x: this.currentX, y: this.currentY, xDelta: this.yDelta, yDelta: this.xDelta, rect: this.lastRect, xDistance: this.currentX - this.startX, yDistance: this.currentY - this.startY, }); this.destroy(); }; /** * bindings * * Bindings for React users to attach their instances to DOM * elements * ```tsx * const Component = () => { * const detector = useDragDetector(options); * return ( * <div {...detector.bindings} /> * ); * } * ``` */ bindings = { ref: this.register, onMouseDown: this.onMouseDown, onTouchStart: this.onMouseDown, }; mouseCoordinates(node, e) { let clientX; let clientY; if ("touches" in e) { ({ clientX, clientY } = e.touches[0]); } else { ({ clientX, clientY } = e); } const rect = this.lastRect || node.getBoundingClientRect(); return [clientX - rect.left, clientY - rect.top, rect]; } listen() { document.addEventListener("mousemove", this.onMouseMove, { passive: true }); document.addEventListener("mouseup", this.onMouseUp, { passive: true }); document.addEventListener("touchmove", this.onMouseMove, { passive: true }); document.addEventListener("touchend", this.onMouseUp, { passive: true }); } static mergeDefaultOptions(options) { return Object.assign({}, this.defaultOptions, options); } }