Conditional default prevention instead of touch-action: none
This commit is contained in:
12
examples/two-finger-pan-scroll.html
Normal file
12
examples/two-finger-pan-scroll.html
Normal file
@@ -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"
|
||||||
|
---
|
||||||
|
<div id="map" class="map"></div>
|
||||||
29
examples/two-finger-pan-scroll.js
Normal file
29
examples/two-finger-pan-scroll.js
Normal file
@@ -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
|
||||||
|
})
|
||||||
|
});
|
||||||
@@ -3,12 +3,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import 'elm-pep';
|
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 MapBrowserEventType from './MapBrowserEventType.js';
|
||||||
import MapBrowserPointerEvent from './MapBrowserPointerEvent.js';
|
import MapBrowserPointerEvent from './MapBrowserPointerEvent.js';
|
||||||
import {listen, unlistenByKey} from './events.js';
|
import {listen, unlistenByKey} from './events.js';
|
||||||
import EventTarget from './events/Target.js';
|
import EventTarget from './events/Target.js';
|
||||||
import PointerEventType from './pointer/EventType.js';
|
import PointerEventType from './pointer/EventType.js';
|
||||||
|
import EventType from './events/EventType.js';
|
||||||
|
|
||||||
class MapBrowserEventHandler extends EventTarget {
|
class MapBrowserEventHandler extends EventTarget {
|
||||||
|
|
||||||
@@ -84,6 +85,12 @@ class MapBrowserEventHandler extends EventTarget {
|
|||||||
PointerEventType.POINTERDOWN,
|
PointerEventType.POINTERDOWN,
|
||||||
this.handlePointerDown_, this);
|
this.handlePointerDown_, this);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {PointerEvent}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.originalPointerMoveEvent_;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {?import("./events.js").EventsKey}
|
* @type {?import("./events.js").EventsKey}
|
||||||
* @private
|
* @private
|
||||||
@@ -92,6 +99,13 @@ class MapBrowserEventHandler extends EventTarget {
|
|||||||
PointerEventType.POINTERMOVE,
|
PointerEventType.POINTERMOVE,
|
||||||
this.relayEvent_, this);
|
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
|
* @private
|
||||||
*/
|
*/
|
||||||
relayEvent_(pointerEvent) {
|
relayEvent_(pointerEvent) {
|
||||||
|
this.originalPointerMoveEvent_ = pointerEvent;
|
||||||
const dragging = !!(this.down_ && this.isMoving_(pointerEvent));
|
const dragging = !!(this.down_ && this.isMoving_(pointerEvent));
|
||||||
this.dispatchEvent(new MapBrowserPointerEvent(
|
this.dispatchEvent(new MapBrowserPointerEvent(
|
||||||
pointerEvent.type, this.map_, pointerEvent, dragging));
|
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
|
* @param {PointerEvent} pointerEvent Pointer
|
||||||
* event.
|
* event.
|
||||||
@@ -271,6 +300,8 @@ class MapBrowserEventHandler extends EventTarget {
|
|||||||
unlistenByKey(this.relayedListenerKey_);
|
unlistenByKey(this.relayedListenerKey_);
|
||||||
this.relayedListenerKey_ = null;
|
this.relayedListenerKey_ = null;
|
||||||
}
|
}
|
||||||
|
this.element_.removeEventListener(EventType.TOUCHMOVE, this.boundHandleTouchMove_);
|
||||||
|
|
||||||
if (this.pointerdownListenerKey_) {
|
if (this.pointerdownListenerKey_) {
|
||||||
unlistenByKey(this.pointerdownListenerKey_);
|
unlistenByKey(this.pointerdownListenerKey_);
|
||||||
this.pointerdownListenerKey_ = null;
|
this.pointerdownListenerKey_ = null;
|
||||||
|
|||||||
@@ -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("./MapBrowserEvent.js").MapBrowserEvent
|
||||||
* @fires import("./MapEvent.js").MapEvent
|
* @fires import("./MapEvent.js").MapEvent
|
||||||
@@ -305,12 +294,6 @@ class PluggableMap extends BaseObject {
|
|||||||
*/
|
*/
|
||||||
this.keyHandlerKeys_ = null;
|
this.keyHandlerKeys_ = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @type {?Array<import("./events.js").EventsKey>}
|
|
||||||
*/
|
|
||||||
this.focusHandlerKeys_ = null;
|
|
||||||
|
|
||||||
const handleBrowserEvent = this.handleBrowserEvent.bind(this);
|
const handleBrowserEvent = this.handleBrowserEvent.bind(this);
|
||||||
this.viewport_.addEventListener(EventType.CONTEXTMENU, handleBrowserEvent, false);
|
this.viewport_.addEventListener(EventType.CONTEXTMENU, handleBrowserEvent, false);
|
||||||
this.viewport_.addEventListener(EventType.WHEEL, handleBrowserEvent,
|
this.viewport_.addEventListener(EventType.WHEEL, handleBrowserEvent,
|
||||||
@@ -939,6 +922,7 @@ class PluggableMap extends BaseObject {
|
|||||||
const type = opt_type || browserEvent.type;
|
const type = opt_type || browserEvent.type;
|
||||||
const mapBrowserEvent = new MapBrowserEvent(type, this, browserEvent);
|
const mapBrowserEvent = new MapBrowserEvent(type, this, browserEvent);
|
||||||
this.handleMapBrowserEvent(mapBrowserEvent);
|
this.handleMapBrowserEvent(mapBrowserEvent);
|
||||||
|
browserEvent.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1044,12 +1028,6 @@ class PluggableMap extends BaseObject {
|
|||||||
targetElement = this.getTargetElement();
|
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_) {
|
if (this.keyHandlerKeys_) {
|
||||||
for (let i = 0, ii = this.keyHandlerKeys_.length; i < ii; ++i) {
|
for (let i = 0, ii = this.keyHandlerKeys_.length; i < ii; ++i) {
|
||||||
unlistenByKey(this.keyHandlerKeys_[i]);
|
unlistenByKey(this.keyHandlerKeys_[i]);
|
||||||
@@ -1078,15 +1056,6 @@ class PluggableMap extends BaseObject {
|
|||||||
if (!this.renderer_) {
|
if (!this.renderer_) {
|
||||||
this.renderer_ = this.createRenderer();
|
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_ ?
|
const keyboardEventTarget = !this.keyboardEventTarget_ ?
|
||||||
targetElement : this.keyboardEventTarget_;
|
targetElement : this.keyboardEventTarget_;
|
||||||
|
|||||||
@@ -136,7 +136,6 @@ class ZoomSlider extends Control {
|
|||||||
thumbElement.setAttribute('type', 'button');
|
thumbElement.setAttribute('type', 'button');
|
||||||
thumbElement.className = className + '-thumb ' + CLASS_UNSELECTABLE;
|
thumbElement.className = className + '-thumb ' + CLASS_UNSELECTABLE;
|
||||||
const containerElement = this.element;
|
const containerElement = this.element;
|
||||||
containerElement.setAttribute('touch-action', 'none');
|
|
||||||
containerElement.className = className + ' ' + CLASS_UNSELECTABLE + ' ' + CLASS_CONTROL;
|
containerElement.className = className + ' ' + CLASS_UNSELECTABLE + ' ' + CLASS_CONTROL;
|
||||||
containerElement.appendChild(thumbElement);
|
containerElement.appendChild(thumbElement);
|
||||||
|
|
||||||
|
|||||||
@@ -34,5 +34,6 @@ export default {
|
|||||||
KEYPRESS: 'keypress',
|
KEYPRESS: 'keypress',
|
||||||
LOAD: 'load',
|
LOAD: 'load',
|
||||||
RESIZE: 'resize',
|
RESIZE: 'resize',
|
||||||
|
TOUCHMOVE: 'touchmove',
|
||||||
WHEEL: 'wheel'
|
WHEEL: 'wheel'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ class DragPan extends PointerInteraction {
|
|||||||
}
|
}
|
||||||
this.lastCentroid = centroid;
|
this.lastCentroid = centroid;
|
||||||
this.lastPointersCount_ = targetPointers.length;
|
this.lastPointersCount_ = targetPointers.length;
|
||||||
|
mapBrowserEvent.originalEvent.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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.
|
* Handle pointer down events.
|
||||||
* @param {import("../MapBrowserPointerEvent.js").default} mapBrowserEvent Event.
|
* @param {import("../MapBrowserPointerEvent.js").default} mapBrowserEvent Event.
|
||||||
|
|||||||
@@ -174,4 +174,21 @@ describe('ol/MapBrowserEventHandler', function() {
|
|||||||
expect(moveToleranceHandler.isMoving_(pointermoveAt2)).to.be(true);
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -666,21 +666,6 @@ describe('ol.Map', function() {
|
|||||||
expect(map.handleResize_).to.be.ok();
|
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() {
|
describe('call setTarget with null', function() {
|
||||||
it('unregisters the viewport resize listener', function() {
|
it('unregisters the viewport resize listener', function() {
|
||||||
map.setTarget(null);
|
map.setTarget(null);
|
||||||
|
|||||||
Reference in New Issue
Block a user