Draggable.js 6.3 KB

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