UNPKG

move.gl

Version:

Motion and Animation Library for Stylescape.

144 lines (127 loc) 4.88 kB
// ============================================================================ // move.gl | Draggable // ============================================================================ // Copyright 2025 Scape Agency BV // Licensed under MIT License // ============================================================================ /** * Options for the Draggable class */ export interface DraggableOptions { /** Whether to constrain dragging to parent bounds */ constrainToParent?: boolean; /** CSS cursor style during drag */ dragCursor?: string; /** Callback when drag starts */ onDragStart?: (x: number, y: number) => void; /** Callback during drag */ onDrag?: (x: number, y: number) => void; /** Callback when drag ends */ onDragEnd?: (x: number, y: number) => void; } /** * Draggable Element Handler * * Provides functionality to make an element draggable within the confines * of its parent container. Supports both mouse and touch interactions, * ensuring usability across different devices. * * @example * ```typescript * const draggable = new Draggable('myElement'); * // Element with id="myElement" is now draggable * ``` */ export class Draggable { private element: HTMLElement; private isDragging: boolean = false; private startX: number = 0; private startY: number = 0; private boundRect: DOMRect; /** * Creates a new Draggable instance. * @param elementId - The ID of the HTML element to make draggable. * @throws Error if element or parent element is not found. */ constructor(elementId: string) { const element = document.getElementById(elementId); if (!element) { throw new Error(`Element with id "${elementId}" not found`); } this.element = element; const parent = this.element.parentElement; if (!parent) { throw new Error('Draggable element must have a parent element'); } this.boundRect = parent.getBoundingClientRect(); this.attachEventListeners(); } /** * Attaches all necessary event listeners for drag functionality. */ private attachEventListeners(): void { this.element.addEventListener('mousedown', this.startDrag); this.element.addEventListener('touchstart', this.startDrag, { passive: false }); document.addEventListener('mouseup', this.stopDrag); document.addEventListener('touchend', this.stopDrag); document.addEventListener('mousemove', this.drag); document.addEventListener('touchmove', this.drag, { passive: false }); } /** * Gets the client coordinates from a mouse or touch event. */ private getClientCoordinates(event: MouseEvent | TouchEvent): { clientX: number; clientY: number } { if ('touches' in event && event.touches.length > 0) { return { clientX: event.touches[0].clientX, clientY: event.touches[0].clientY }; } return { clientX: (event as MouseEvent).clientX, clientY: (event as MouseEvent).clientY }; } /** * Initiates the drag operation. */ private startDrag = (event: MouseEvent | TouchEvent): void => { const coords = this.getClientCoordinates(event); this.isDragging = true; this.startX = coords.clientX - this.element.offsetLeft; this.startY = coords.clientY - this.element.offsetTop; event.preventDefault(); }; /** * Handles the drag movement. */ private drag = (event: MouseEvent | TouchEvent): void => { if (!this.isDragging) return; const coords = this.getClientCoordinates(event); let x = coords.clientX - this.startX; let y = coords.clientY - this.startY; // Constrain the movement within the bounds of the element's parent x = Math.max(this.boundRect.left, Math.min(x, this.boundRect.right - this.element.offsetWidth)); y = Math.max(this.boundRect.top, Math.min(y, this.boundRect.bottom - this.element.offsetHeight)); this.element.style.left = `${x}px`; this.element.style.top = `${y}px`; }; /** * Stops the drag operation. */ private stopDrag = (): void => { this.isDragging = false; }; /** * Removes all event listeners and cleans up. */ public destroy(): void { this.element.removeEventListener('mousedown', this.startDrag); this.element.removeEventListener('touchstart', this.startDrag); document.removeEventListener('mouseup', this.stopDrag); document.removeEventListener('touchend', this.stopDrag); document.removeEventListener('mousemove', this.drag); document.removeEventListener('touchmove', this.drag); } } export default Draggable;