UNPKG

leaflet

Version:

JavaScript library for mobile-friendly interactive maps

202 lines (154 loc) 5.63 kB
import {Evented} from '../core/Events.js'; import * as DomEvent from './DomEvent.js'; import * as DomUtil from './DomUtil.js'; import * as Util from '../core/Util.js'; import {Point} from '../geometry/Point.js'; import * as PointerEvents from './DomEvent.PointerEvents.js'; /* * @class Draggable * @inherits Evented * * A class for making DOM elements draggable. * Used internally for map and marker dragging. Works on any DOM element * * @example * ```js * const draggable = new Draggable(elementToDrag); * draggable.enable(); * ``` */ export class Draggable extends Evented { static { this.setDefaultOptions({ // @section // @aka Draggable options // @option clickTolerance: Number = 3 // The max number of pixels a user can shift the pointer during a click // for it to be considered a valid click (as opposed to a pointer drag). clickTolerance: 3 }); } // @constructor Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options) // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default). initialize(element, dragStartTarget, preventOutline, options) { Util.setOptions(this, options); this._element = element; this._dragStartTarget = dragStartTarget ?? element; this._preventOutline = preventOutline; } // @method enable() // Enables the dragging ability enable() { if (this._enabled) { return; } DomEvent.on(this._dragStartTarget, 'pointerdown', this._onDown, this); this._enabled = true; } // @method disable() // Disables the dragging ability disable() { if (!this._enabled) { return; } // If we're currently dragging this draggable, // disabling it counts as first ending the drag. if (Draggable._dragging === this) { this.finishDrag(true); } DomEvent.off(this._dragStartTarget, 'pointerdown', this._onDown, this); this._enabled = false; this._moved = false; } _onDown(e) { this._moved = false; if (this._element.classList.contains('leaflet-zoom-anim')) { return; } if (PointerEvents.getPointers().length !== 1) { // Finish dragging to avoid conflict with touchZoom if (Draggable._dragging === this) { this.finishDrag(); } return; } if (Draggable._dragging || e.shiftKey || (e.button !== 0 && e.pointerType !== 'touch')) { return; } Draggable._dragging = this; // Prevent dragging multiple objects at once. if (this._preventOutline) { DomUtil.preventOutline(this._element); } DomUtil.disableImageDrag(); DomUtil.disableTextSelection(); if (this._moving) { return; } // @event down: Event // Fired when a drag is about to start. this.fire('down'); const sizedParent = DomUtil.getSizedParentNode(this._element); this._startPoint = new Point(e.clientX, e.clientY); this._startPos = DomUtil.getPosition(this._element); // Cache the scale, so that we can continuously compensate for it during drag (_onMove). this._parentScale = DomUtil.getScale(sizedParent); DomEvent.on(document, 'pointermove', this._onMove, this); DomEvent.on(document, 'pointerup pointercancel', this._onUp, this); } _onMove(e) { if (PointerEvents.getPointers().length > 1) { this._moved = true; return; } const offset = new Point(e.clientX, e.clientY)._subtract(this._startPoint); if (!offset.x && !offset.y) { return; } if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; } // We assume that the parent container's position, border and scale do not change for the duration of the drag. // Therefore there is no need to account for the position and border (they are eliminated by the subtraction) // and we can use the cached value for the scale. offset.x /= this._parentScale.x; offset.y /= this._parentScale.y; if (e.cancelable) { DomEvent.preventDefault(e); } if (!this._moved) { // @event dragstart: Event // Fired when a drag starts this.fire('dragstart'); this._moved = true; document.body.classList.add('leaflet-dragging'); this._lastTarget = e.target ?? e.srcElement; this._lastTarget.classList.add('leaflet-drag-target'); } this._newPos = this._startPos.add(offset); this._moving = true; this._lastEvent = e; this._updatePosition(); } _updatePosition() { const e = {originalEvent: this._lastEvent}; // @event predrag: Event // Fired continuously during dragging *before* each corresponding // update of the element's position. this.fire('predrag', e); DomUtil.setPosition(this._element, this._newPos); // @event drag: Event // Fired continuously during dragging. this.fire('drag', e); } _onUp() { this.finishDrag(); } finishDrag(noInertia) { document.body.classList.remove('leaflet-dragging'); if (this._lastTarget) { this._lastTarget.classList.remove('leaflet-drag-target'); this._lastTarget = null; } DomEvent.off(document, 'pointermove', this._onMove, this); DomEvent.off(document, 'pointerup pointercancel', this._onUp, this); DomUtil.enableImageDrag(); DomUtil.enableTextSelection(); const fireDragend = this._moved && this._moving; this._moving = false; Draggable._dragging = false; if (fireDragend) { // @event dragend: DragEndEvent // Fired when the drag ends. this.fire('dragend', { noInertia, distance: this._newPos.distanceTo(this._startPos) }); } } }