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 {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;
|
||||
|
||||
@@ -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<import("./events.js").EventsKey>}
|
||||
*/
|
||||
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_;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -34,5 +34,6 @@ export default {
|
||||
KEYPRESS: 'keypress',
|
||||
LOAD: 'load',
|
||||
RESIZE: 'resize',
|
||||
TOUCHMOVE: 'touchmove',
|
||||
WHEEL: 'wheel'
|
||||
};
|
||||
|
||||
@@ -115,6 +115,7 @@ class DragPan extends PointerInteraction {
|
||||
}
|
||||
this.lastCentroid = centroid;
|
||||
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.
|
||||
* @param {import("../MapBrowserPointerEvent.js").default} mapBrowserEvent Event.
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user