Conditional default prevention instead of touch-action: none

This commit is contained in:
Andreas Hocevar
2019-11-25 00:27:51 +01:00
parent 0ed57b8b7c
commit 88b8b2f7cb
10 changed files with 103 additions and 49 deletions

View 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>

View 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
})
});

View File

@@ -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;

View File

@@ -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_;

View File

@@ -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);

View File

@@ -34,5 +34,6 @@ export default {
KEYPRESS: 'keypress',
LOAD: 'load',
RESIZE: 'resize',
TOUCHMOVE: 'touchmove',
WHEEL: 'wheel'
};

View File

@@ -115,6 +115,7 @@ class DragPan extends PointerInteraction {
}
this.lastCentroid = centroid;
this.lastPointersCount_ = targetPointers.length;
mapBrowserEvent.originalEvent.preventDefault();
}
/**

View File

@@ -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.

View File

@@ -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);
});
});
});

View File

@@ -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);