diff --git a/build/deps.js b/build/deps.js index 8019c1f9..2253b3cf 100644 --- a/build/deps.js +++ b/build/deps.js @@ -178,9 +178,10 @@ var deps = { 'dom/DomEvent.DoubleTap.js', 'dom/DomEvent.MsTouch.js', 'core/Handler.js', - 'map/handler/Map.TouchZoom.js'], + 'map/handler/Map.TouchZoom.js', + 'map/handler/Map.Tap.js'], deps: ['AnimationZoom'], - desc: 'Enables smooth touch zooming on iOS and IE10 and double tap on iOS/IE10/Android.' + desc: 'Enables smooth touch zoom / tap / longhold / doubletap on iOS, IE10, Android.' }, BoxZoom: { diff --git a/src/dom/Draggable.js b/src/dom/Draggable.js index 743e1e6f..a9900c31 100644 --- a/src/dom/Draggable.js +++ b/src/dom/Draggable.js @@ -16,14 +16,12 @@ L.Draggable = L.Class.extend({ mousedown: 'mousemove', touchstart: 'touchmove', MSPointerDown: 'touchmove' - }, - TAP_TOLERANCE: 15 + } }, - initialize: function (element, dragStartTarget, longPress) { + initialize: function (element, dragStartTarget) { this._element = element; this._dragStartTarget = dragStartTarget || element; - this._longPress = longPress && !L.Browser.msTouch; }, enable: function () { @@ -51,23 +49,11 @@ L.Draggable = L.Class.extend({ if (e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } L.DomEvent - .preventDefault(e) .stopPropagation(e); if (L.Draggable._disabled) { return; } - this._simulateClick = true; - - var touchesNum = (e.touches && e.touches.length) || 0; - - // don't simulate click or track longpress if more than 1 touch - if (touchesNum > 1) { - this._simulateClick = false; - clearTimeout(this._longPressTimeout); - return; - } - - var first = touchesNum === 1 ? e.touches[0] : e, + var first = e.touches ? e.touches[0] : e, el = first.target; // if touching a link, highlight it @@ -82,20 +68,6 @@ L.Draggable = L.Class.extend({ this._startPoint = new L.Point(first.clientX, first.clientY); this._startPos = this._newPos = L.DomUtil.getPosition(this._element); - // touch contextmenu event emulation - if (touchesNum === 1 && L.Browser.touch && this._longPress) { - - this._longPressTimeout = setTimeout(L.bind(function () { - var dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0; - - if (dist < L.Draggable.TAP_TOLERANCE) { - this._simulateClick = false; - this._onUp(); - this._simulateEvent('contextmenu', first); - } - }, this), 1000); - } - L.DomEvent .on(document, L.Draggable.MOVE[e.type], this._onMove, this) .on(document, L.Draggable.END[e.type], this._onUp, this); @@ -141,33 +113,13 @@ L.Draggable = L.Class.extend({ this.fire('drag'); }, - _onUp: function (e) { - var first, el, dist, simulateClickTouch, i; - - clearTimeout(this._longPressTimeout); - - if (this._simulateClick && e.changedTouches) { - - dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0; - first = e.changedTouches[0]; - el = first.target; - - if (el.tagName.toLowerCase() === 'a') { - L.DomUtil.removeClass(el, 'leaflet-active'); - } - - // simulate click if the touch didn't move too much - if (dist < L.Draggable.TAP_TOLERANCE) { - simulateClickTouch = true; - } - } - + _onUp: function () { if (!L.Browser.touch) { L.DomUtil.enableTextSelection(); L.DomUtil.removeClass(document.body, 'leaflet-dragging'); } - for (i in L.Draggable.MOVE) { + for (var i in L.Draggable.MOVE) { L.DomEvent .off(document, L.Draggable.MOVE[i], this._onMove) .off(document, L.Draggable.END[i], this._onUp); @@ -183,22 +135,5 @@ L.Draggable = L.Class.extend({ } this._moving = false; - - if (simulateClickTouch) { - this._moved = false; - this._simulateEvent('click', first); - } - }, - - _simulateEvent: function (type, e) { - var simulatedEvent = document.createEvent('MouseEvents'); - - simulatedEvent.initMouseEvent( - type, true, true, window, 1, - e.screenX, e.screenY, - e.clientX, e.clientY, - false, false, false, false, 0, null); - - e.target.dispatchEvent(simulatedEvent); } }); diff --git a/src/map/Map.js b/src/map/Map.js index 32639f32..443b2637 100644 --- a/src/map/Map.js +++ b/src/map/Map.js @@ -27,6 +27,10 @@ L.Map = L.Class.extend({ this._initLayout(); this._initEvents(); + if (L.DomEvent.enableTapHacks) { + L.DomEvent.enableTapHacks(this._container); + } + if (options.maxBounds) { this.setMaxBounds(options.maxBounds); } @@ -674,7 +678,7 @@ L.Map = L.Class.extend({ _onMouseClick: function (e) { // jshint camelcase: false - if (!this._loaded || (this.dragging && this.dragging.moved()) || e._leaflet_stop) { return; } + if (!this._loaded || (!e._simulated && this.dragging && this.dragging.moved()) || e._leaflet_stop) { return; } this.fire('preclick'); this._fireMouseEvent(e); diff --git a/src/map/handler/Map.Drag.js b/src/map/handler/Map.Drag.js index a4297d0e..0a1d202e 100644 --- a/src/map/handler/Map.Drag.js +++ b/src/map/handler/Map.Drag.js @@ -11,8 +11,6 @@ L.Map.mergeOptions({ inertiaThreshold: L.Browser.touch ? 32 : 18, // ms easeLinearity: 0.25, - longPress: true, - // TODO refactor, move to CRS worldCopyJump: false }); @@ -22,7 +20,7 @@ L.Map.Drag = L.Handler.extend({ if (!this._draggable) { var map = this._map; - this._draggable = new L.Draggable(map._mapPane, map._container, map.options.longPress); + this._draggable = new L.Draggable(map._mapPane, map._container); this._draggable.on({ 'dragstart': this._onDragStart, diff --git a/src/map/handler/Map.Tap.js b/src/map/handler/Map.Tap.js new file mode 100644 index 00000000..a06f1ae9 --- /dev/null +++ b/src/map/handler/Map.Tap.js @@ -0,0 +1,108 @@ +/* + * L.Map.Tap is used to enable mobile hacks like quick taps and long hold. + */ + +L.Map.mergeOptions({ + tap: true, + tapTolerance: 15 +}); + +L.Map.Tap = L.Handler.extend({ + addHooks: function () { + if (!L.Browser.touch) { return; } + L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this); + }, + + removeHooks: function () { + if (!L.Browser.touch) { return; } + L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this); + }, + + _onDown: function (e) { + if (!e.touches) { return; } + + L.DomEvent.preventDefault(e); + + this._fireClick = true; + + // don't simulate click or track longpress if more than 1 touch + if (e.touches.length > 1) { + this._fireClick = false; + clearTimeout(this._holdTimeout); + return; + } + + var first = e.touches[0], + el = first.target; + + this._startPos = this._newPos = new L.Point(first.clientX, first.clientY); + + // if touching a link, highlight it + if (el.tagName.toLowerCase() === 'a') { + L.DomUtil.addClass(el, 'leaflet-active'); + } + + // simulate long hold but setting a timeout + if (!L.Browser.msTouch) { + this._holdTimeout = setTimeout(L.bind(function () { + if (this._isTapValid()) { + this._fireClick = false; + this._onUp(); + this._simulateEvent('contextmenu', first); + } + }, this), 1000); + } + + L.DomEvent + .on(document, 'touchmove', this._onMove, this) + .on(document, 'touchend', this._onUp, this); + }, + + _onUp: function (e) { + clearTimeout(this._holdTimeout); + + L.DomEvent + .off(document, 'touchmove', this._onMove, this) + .off(document, 'touchend', this._onUp, this); + + if (this._fireClick && e && e.changedTouches) { + + var first = e.changedTouches[0], + el = first.target; + + if (el.tagName.toLowerCase() === 'a') { + L.DomUtil.removeClass(el, 'leaflet-active'); + } + + // simulate click if the touch didn't move too much + if (this._isTapValid()) { + this._simulateEvent('click', first); + } + } + }, + + _isTapValid: function () { + return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance; + }, + + _onMove: function (e) { + var first = e.touches[0]; + this._newPos = new L.Point(first.clientX, first.clientY); + }, + + _simulateEvent: function (type, e) { + var simulatedEvent = document.createEvent('MouseEvents'); + + simulatedEvent._simulated = true; + + simulatedEvent.initMouseEvent( + type, true, true, window, 1, + e.screenX, e.screenY, + e.clientX, e.clientY, + false, false, false, false, 0, null); + + e.target.dispatchEvent(simulatedEvent); + } +}); + +L.Map.addInitHook('addHandler', 'tap', L.Map.Tap);