From 88b8b2f7cb95fe6c728767dc5d1194bc04139b60 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Mon, 25 Nov 2019 00:27:51 +0100 Subject: [PATCH] Conditional default prevention instead of touch-action: none --- examples/two-finger-pan-scroll.html | 12 ++++++++ examples/two-finger-pan-scroll.js | 29 ++++++++++++++++++ src/ol/MapBrowserEventHandler.js | 33 ++++++++++++++++++++- src/ol/PluggableMap.js | 33 +-------------------- src/ol/control/ZoomSlider.js | 1 - src/ol/events/EventType.js | 1 + src/ol/interaction/DragPan.js | 1 + src/ol/interaction/Pointer.js | 10 +++++++ test/spec/ol/MapBrowserEventHandler.test.js | 17 +++++++++++ test/spec/ol/map.test.js | 15 ---------- 10 files changed, 103 insertions(+), 49 deletions(-) create mode 100644 examples/two-finger-pan-scroll.html create mode 100644 examples/two-finger-pan-scroll.js diff --git a/examples/two-finger-pan-scroll.html b/examples/two-finger-pan-scroll.html new file mode 100644 index 0000000000..f3faf89318 --- /dev/null +++ b/examples/two-finger-pan-scroll.html @@ -0,0 +1,12 @@ +--- +layout: example.html +title: Panning and page scrolling +shortdesc: Shows a map that allows page scrolling unless two fingers or Cmd/Ctrl are used to pan the map. +docs: > + This example shows a common behavior for page scrolling: on touch devices, when one finger + is placed on the map, it can be used to scroll the page. Only two fingers will perform drag pan. + For mouse or trackpad devices, the platform modifier key (Cmd or Ctrl) will enable drag pan on + the map, otherwise the page will scroll. +tags: "trackpad, mousewheel, zoom, scroll, page" +--- +
diff --git a/examples/two-finger-pan-scroll.js b/examples/two-finger-pan-scroll.js new file mode 100644 index 0000000000..1207d28799 --- /dev/null +++ b/examples/two-finger-pan-scroll.js @@ -0,0 +1,29 @@ +import Map from '../src/ol/Map.js'; +import View from '../src/ol/View.js'; +import TileLayer from '../src/ol/layer/Tile.js'; +import OSM from '../src/ol/source/OSM.js'; +import {defaults, DragPan, MouseWheelZoom} from '../src/ol/interaction.js'; +import {platformModifierKeyOnly} from '../src/ol/events/condition.js'; + +const map = new Map({ + interactions: defaults({dragPan: false, mouseWheelZoom: false}).extend([ + new DragPan({ + condition: function(event) { + return this.getPointerCount() === 2 || platformModifierKeyOnly(event); + } + }), + new MouseWheelZoom({ + condition: platformModifierKeyOnly + }) + ]), + layers: [ + new TileLayer({ + source: new OSM() + }) + ], + target: 'map', + view: new View({ + center: [0, 0], + zoom: 2 + }) +}); diff --git a/src/ol/MapBrowserEventHandler.js b/src/ol/MapBrowserEventHandler.js index e56e178488..e19b74e55e 100644 --- a/src/ol/MapBrowserEventHandler.js +++ b/src/ol/MapBrowserEventHandler.js @@ -3,12 +3,13 @@ */ import 'elm-pep'; -import {DEVICE_PIXEL_RATIO} from './has.js'; +import {DEVICE_PIXEL_RATIO, PASSIVE_EVENT_LISTENERS} from './has.js'; import MapBrowserEventType from './MapBrowserEventType.js'; import MapBrowserPointerEvent from './MapBrowserPointerEvent.js'; import {listen, unlistenByKey} from './events.js'; import EventTarget from './events/Target.js'; import PointerEventType from './pointer/EventType.js'; +import EventType from './events/EventType.js'; class MapBrowserEventHandler extends EventTarget { @@ -84,6 +85,12 @@ class MapBrowserEventHandler extends EventTarget { PointerEventType.POINTERDOWN, this.handlePointerDown_, this); + /** + * @type {PointerEvent} + * @private + */ + this.originalPointerMoveEvent_; + /** * @type {?import("./events.js").EventsKey} * @private @@ -92,6 +99,13 @@ class MapBrowserEventHandler extends EventTarget { PointerEventType.POINTERMOVE, this.relayEvent_, this); + /** + * @private + */ + this.boundHandleTouchMove_ = this.handleTouchMove_.bind(this); + + this.element_.addEventListener(EventType.TOUCHMOVE, this.boundHandleTouchMove_, + PASSIVE_EVENT_LISTENERS ? {passive: false} : false); } /** @@ -246,11 +260,26 @@ class MapBrowserEventHandler extends EventTarget { * @private */ relayEvent_(pointerEvent) { + this.originalPointerMoveEvent_ = pointerEvent; const dragging = !!(this.down_ && this.isMoving_(pointerEvent)); this.dispatchEvent(new MapBrowserPointerEvent( pointerEvent.type, this.map_, pointerEvent, dragging)); } + /** + * Flexible handling of a `touch-action: none` css equivalent: because calling + * `preventDefault()` on a `pointermove` event does not stop native page scrolling + * and zooming, we also listen for `touchmove` and call `preventDefault()` on it + * when an interaction (currently `DragPan` handles the event. + * @param {TouchEvent} event Event. + * @private + */ + handleTouchMove_(event) { + if (this.originalPointerMoveEvent_.defaultPrevented) { + event.preventDefault(); + } + } + /** * @param {PointerEvent} pointerEvent Pointer * event. @@ -271,6 +300,8 @@ class MapBrowserEventHandler extends EventTarget { unlistenByKey(this.relayedListenerKey_); this.relayedListenerKey_ = null; } + this.element_.removeEventListener(EventType.TOUCHMOVE, this.boundHandleTouchMove_); + if (this.pointerdownListenerKey_) { unlistenByKey(this.pointerdownListenerKey_); this.pointerdownListenerKey_ = null; diff --git a/src/ol/PluggableMap.js b/src/ol/PluggableMap.js index a6e524988b..99db86f835 100644 --- a/src/ol/PluggableMap.js +++ b/src/ol/PluggableMap.js @@ -131,17 +131,6 @@ import {toUserCoordinate, fromUserCoordinate} from './proj.js'; */ -/** - * @param {HTMLElement} element Element. - * @param {string} touchAction Value for `touch-action'. - */ -function setTouchAction(element, touchAction) { - element.style.msTouchAction = touchAction; - element.style.touchAction = touchAction; - element.setAttribute('touch-action', touchAction); -} - - /** * @fires import("./MapBrowserEvent.js").MapBrowserEvent * @fires import("./MapEvent.js").MapEvent @@ -305,12 +294,6 @@ class PluggableMap extends BaseObject { */ this.keyHandlerKeys_ = null; - /** - * @private - * @type {?Array} - */ - this.focusHandlerKeys_ = null; - const handleBrowserEvent = this.handleBrowserEvent.bind(this); this.viewport_.addEventListener(EventType.CONTEXTMENU, handleBrowserEvent, false); this.viewport_.addEventListener(EventType.WHEEL, handleBrowserEvent, @@ -939,6 +922,7 @@ class PluggableMap extends BaseObject { const type = opt_type || browserEvent.type; const mapBrowserEvent = new MapBrowserEvent(type, this, browserEvent); this.handleMapBrowserEvent(mapBrowserEvent); + browserEvent.preventDefault(); } /** @@ -1044,12 +1028,6 @@ class PluggableMap extends BaseObject { targetElement = this.getTargetElement(); } - if (this.focusHandlerKeys_) { - for (let i = 0, ii = this.focusHandlerKeys_.length; i < ii; ++i) { - unlistenByKey(this.focusHandlerKeys_[i]); - } - this.focusHandlerKeys_ = null; - } if (this.keyHandlerKeys_) { for (let i = 0, ii = this.keyHandlerKeys_.length; i < ii; ++i) { unlistenByKey(this.keyHandlerKeys_[i]); @@ -1078,15 +1056,6 @@ class PluggableMap extends BaseObject { if (!this.renderer_) { this.renderer_ = this.createRenderer(); } - let hasFocus = true; - if (targetElement.hasAttribute('tabindex')) { - hasFocus = document.activeElement === targetElement; - this.focusHandlerKeys_ = [ - listen(targetElement, EventType.FOCUS, setTouchAction.bind(this, this.viewport_, 'none')), - listen(targetElement, EventType.BLUR, setTouchAction.bind(this, this.viewport_, 'auto')) - ]; - } - setTouchAction(this.viewport_, hasFocus ? 'none' : 'auto'); const keyboardEventTarget = !this.keyboardEventTarget_ ? targetElement : this.keyboardEventTarget_; diff --git a/src/ol/control/ZoomSlider.js b/src/ol/control/ZoomSlider.js index a87da521c0..dcb8b4c223 100644 --- a/src/ol/control/ZoomSlider.js +++ b/src/ol/control/ZoomSlider.js @@ -136,7 +136,6 @@ class ZoomSlider extends Control { thumbElement.setAttribute('type', 'button'); thumbElement.className = className + '-thumb ' + CLASS_UNSELECTABLE; const containerElement = this.element; - containerElement.setAttribute('touch-action', 'none'); containerElement.className = className + ' ' + CLASS_UNSELECTABLE + ' ' + CLASS_CONTROL; containerElement.appendChild(thumbElement); diff --git a/src/ol/events/EventType.js b/src/ol/events/EventType.js index 26594c960b..8959635887 100644 --- a/src/ol/events/EventType.js +++ b/src/ol/events/EventType.js @@ -34,5 +34,6 @@ export default { KEYPRESS: 'keypress', LOAD: 'load', RESIZE: 'resize', + TOUCHMOVE: 'touchmove', WHEEL: 'wheel' }; diff --git a/src/ol/interaction/DragPan.js b/src/ol/interaction/DragPan.js index 12f526548b..1c98592308 100644 --- a/src/ol/interaction/DragPan.js +++ b/src/ol/interaction/DragPan.js @@ -115,6 +115,7 @@ class DragPan extends PointerInteraction { } this.lastCentroid = centroid; this.lastPointersCount_ = targetPointers.length; + mapBrowserEvent.originalEvent.preventDefault(); } /** diff --git a/src/ol/interaction/Pointer.js b/src/ol/interaction/Pointer.js index 38deafb79f..8f262e350b 100644 --- a/src/ol/interaction/Pointer.js +++ b/src/ol/interaction/Pointer.js @@ -95,6 +95,16 @@ class PointerInteraction extends Interaction { } + /** + * Returns the current number of pointers involved in the interaction, + * e.g. `2` when two fingers are used. + * @return {number} The number of pointers. + * @api + */ + getPointerCount() { + return this.targetPointers.length; + } + /** * Handle pointer down events. * @param {import("../MapBrowserPointerEvent.js").default} mapBrowserEvent Event. diff --git a/test/spec/ol/MapBrowserEventHandler.test.js b/test/spec/ol/MapBrowserEventHandler.test.js index 15d6db9586..b8d33fd380 100644 --- a/test/spec/ol/MapBrowserEventHandler.test.js +++ b/test/spec/ol/MapBrowserEventHandler.test.js @@ -174,4 +174,21 @@ describe('ol/MapBrowserEventHandler', function() { expect(moveToleranceHandler.isMoving_(pointermoveAt2)).to.be(true); }); }); + + describe('handleTouchMove_', function() { + let handler; + beforeEach(function() { + handler = new MapBrowserEventHandler(new Map({})); + }); + it('prevents default on touchmove event', function() { + handler.originalPointerMoveEvent_ = { + defaultPrevented: true + }; + const event = { + preventDefault: sinon.spy() + }; + handler.handleTouchMove_(event); + expect(event.preventDefault.callCount).to.be(1); + }); + }); }); diff --git a/test/spec/ol/map.test.js b/test/spec/ol/map.test.js index 974a396beb..b6202a11f8 100644 --- a/test/spec/ol/map.test.js +++ b/test/spec/ol/map.test.js @@ -666,21 +666,6 @@ describe('ol.Map', function() { expect(map.handleResize_).to.be.ok(); }); - it('handles touch-action on focus and blur', function() { - expect(map.focusHandlerKeys_).to.be(null); - expect(map.getViewport().getAttribute('touch-action')).to.be('none'); - const target = document.createElement('div'); - target.setAttribute('tabindex', 1); - map.setTarget(target); - expect(Array.isArray(map.focusHandlerKeys_)).to.be(true); - expect(map.getViewport().getAttribute('touch-action')).to.be('auto'); - target.dispatchEvent(new Event('focus')); - expect(map.getViewport().getAttribute('touch-action')).to.be('none'); - map.setTarget(null); - expect(map.focusHandlerKeys_).to.be(null); - expect(map.getViewport().getAttribute('touch-action')).to.be('none'); - }); - describe('call setTarget with null', function() { it('unregisters the viewport resize listener', function() { map.setTarget(null);