UNPKG

7.34 kBJavaScriptView Raw
1import {Evented} from '../core/Events';
2import * as Browser from '../core/Browser';
3import * as DomEvent from './DomEvent';
4import * as DomUtil from './DomUtil';
5import * as Util from '../core/Util';
6import {Point} from '../geometry/Point';
7
8/*
9 * @class Draggable
10 * @aka L.Draggable
11 * @inherits Evented
12 *
13 * A class for making DOM elements draggable (including touch support).
14 * Used internally for map and marker dragging. Only works for elements
15 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
16 *
17 * @example
18 * ```js
19 * var draggable = new L.Draggable(elementToDrag);
20 * draggable.enable();
21 * ```
22 */
23
24var START = Browser.touch ? 'touchstart mousedown' : 'mousedown';
25var END = {
26 mousedown: 'mouseup',
27 touchstart: 'touchend',
28 pointerdown: 'touchend',
29 MSPointerDown: 'touchend'
30};
31var MOVE = {
32 mousedown: 'mousemove',
33 touchstart: 'touchmove',
34 pointerdown: 'touchmove',
35 MSPointerDown: 'touchmove'
36};
37
38
39export var Draggable = Evented.extend({
40
41 options: {
42 // @section
43 // @aka Draggable options
44 // @option clickTolerance: Number = 3
45 // The max number of pixels a user can shift the mouse pointer during a click
46 // for it to be considered a valid click (as opposed to a mouse drag).
47 clickTolerance: 3
48 },
49
50 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
51 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
52 initialize: function (element, dragStartTarget, preventOutline, options) {
53 Util.setOptions(this, options);
54
55 this._element = element;
56 this._dragStartTarget = dragStartTarget || element;
57 this._preventOutline = preventOutline;
58 },
59
60 // @method enable()
61 // Enables the dragging ability
62 enable: function () {
63 if (this._enabled) { return; }
64
65 DomEvent.on(this._dragStartTarget, START, this._onDown, this);
66
67 this._enabled = true;
68 },
69
70 // @method disable()
71 // Disables the dragging ability
72 disable: function () {
73 if (!this._enabled) { return; }
74
75 // If we're currently dragging this draggable,
76 // disabling it counts as first ending the drag.
77 if (Draggable._dragging === this) {
78 this.finishDrag();
79 }
80
81 DomEvent.off(this._dragStartTarget, START, this._onDown, this);
82
83 this._enabled = false;
84 this._moved = false;
85 },
86
87 _onDown: function (e) {
88 // Ignore simulated events, since we handle both touch and
89 // mouse explicitly; otherwise we risk getting duplicates of
90 // touch events, see #4315.
91 // Also ignore the event if disabled; this happens in IE11
92 // under some circumstances, see #3666.
93 if (e._simulated || !this._enabled) { return; }
94
95 this._moved = false;
96
97 if (DomUtil.hasClass(this._element, 'leaflet-zoom-anim')) { return; }
98
99 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
100 Draggable._dragging = this; // Prevent dragging multiple objects at once.
101
102 if (this._preventOutline) {
103 DomUtil.preventOutline(this._element);
104 }
105
106 DomUtil.disableImageDrag();
107 DomUtil.disableTextSelection();
108
109 if (this._moving) { return; }
110
111 // @event down: Event
112 // Fired when a drag is about to start.
113 this.fire('down');
114
115 var first = e.touches ? e.touches[0] : e,
116 sizedParent = DomUtil.getSizedParentNode(this._element);
117
118 this._startPoint = new Point(first.clientX, first.clientY);
119
120 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
121 this._parentScale = DomUtil.getScale(sizedParent);
122
123 DomEvent.on(document, MOVE[e.type], this._onMove, this);
124 DomEvent.on(document, END[e.type], this._onUp, this);
125 },
126
127 _onMove: function (e) {
128 // Ignore simulated events, since we handle both touch and
129 // mouse explicitly; otherwise we risk getting duplicates of
130 // touch events, see #4315.
131 // Also ignore the event if disabled; this happens in IE11
132 // under some circumstances, see #3666.
133 if (e._simulated || !this._enabled) { return; }
134
135 if (e.touches && e.touches.length > 1) {
136 this._moved = true;
137 return;
138 }
139
140 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
141 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
142
143 if (!offset.x && !offset.y) { return; }
144 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
145
146 // We assume that the parent container's position, border and scale do not change for the duration of the drag.
147 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
148 // and we can use the cached value for the scale.
149 offset.x /= this._parentScale.x;
150 offset.y /= this._parentScale.y;
151
152 DomEvent.preventDefault(e);
153
154 if (!this._moved) {
155 // @event dragstart: Event
156 // Fired when a drag starts
157 this.fire('dragstart');
158
159 this._moved = true;
160 this._startPos = DomUtil.getPosition(this._element).subtract(offset);
161
162 DomUtil.addClass(document.body, 'leaflet-dragging');
163
164 this._lastTarget = e.target || e.srcElement;
165 // IE and Edge do not give the <use> element, so fetch it
166 // if necessary
167 if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
168 this._lastTarget = this._lastTarget.correspondingUseElement;
169 }
170 DomUtil.addClass(this._lastTarget, 'leaflet-drag-target');
171 }
172
173 this._newPos = this._startPos.add(offset);
174 this._moving = true;
175
176 Util.cancelAnimFrame(this._animRequest);
177 this._lastEvent = e;
178 this._animRequest = Util.requestAnimFrame(this._updatePosition, this, true);
179 },
180
181 _updatePosition: function () {
182 var e = {originalEvent: this._lastEvent};
183
184 // @event predrag: Event
185 // Fired continuously during dragging *before* each corresponding
186 // update of the element's position.
187 this.fire('predrag', e);
188 DomUtil.setPosition(this._element, this._newPos);
189
190 // @event drag: Event
191 // Fired continuously during dragging.
192 this.fire('drag', e);
193 },
194
195 _onUp: function (e) {
196 // Ignore simulated events, since we handle both touch and
197 // mouse explicitly; otherwise we risk getting duplicates of
198 // touch events, see #4315.
199 // Also ignore the event if disabled; this happens in IE11
200 // under some circumstances, see #3666.
201 if (e._simulated || !this._enabled) { return; }
202 this.finishDrag();
203 },
204
205 finishDrag: function () {
206 DomUtil.removeClass(document.body, 'leaflet-dragging');
207
208 if (this._lastTarget) {
209 DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target');
210 this._lastTarget = null;
211 }
212
213 for (var i in MOVE) {
214 DomEvent.off(document, MOVE[i], this._onMove, this);
215 DomEvent.off(document, END[i], this._onUp, this);
216 }
217
218 DomUtil.enableImageDrag();
219 DomUtil.enableTextSelection();
220
221 if (this._moved && this._moving) {
222 // ensure drag is not fired after dragend
223 Util.cancelAnimFrame(this._animRequest);
224
225 // @event dragend: DragEndEvent
226 // Fired when the drag ends.
227 this.fire('dragend', {
228 distance: this._newPos.distanceTo(this._startPos)
229 });
230 }
231
232 this._moving = false;
233 Draggable._dragging = false;
234 }
235
236});