Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add touch pan blocker to gesture handling for touch devices #11116

Merged
merged 26 commits into from
Oct 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5b5c2d0
initial implementation of touch pan blocker of gesture handler
Oct 4, 2021
294331a
add default locale to touch pan message -- needs refactoring (some re…
Oct 4, 2021
2afcb0d
Merge branch 'main' into avpeery/two-fingers-to-move
Oct 8, 2021
43c6b06
allow tap zoom with gesture handler implemented, fix flickering alert
Oct 12, 2021
b77f80d
Removed unneeded tap references in touch pan
Oct 12, 2021
5c8819a
added touch_pan gesture handling unit tests
Oct 12, 2021
2e64d7d
increased timeout for less flickering
Oct 12, 2021
56a3a06
added release testing page for gesture handling
Oct 12, 2021
42a093e
updated debug page for gesture handling
Oct 12, 2021
e8592d2
updated documentation for gesture handling to incoporate touch pan
Oct 13, 2021
e1d0c6c
git removed file name change not using
Oct 13, 2021
cb1cf61
fix scrollable page issue by toggling touch-action css property
Oct 13, 2021
8fd5561
Merge branch 'main' into avpeery/two-fingers-to-move
Oct 13, 2021
73d838a
override touch-action with specificity, next step - not showing alert…
Oct 14, 2021
78eb8f7
added in removing extra class
Oct 14, 2021
fc85d8b
use mapTouches and add comments
Oct 14, 2021
c77995d
add comments
Oct 14, 2021
6d4c404
fixed touch pitch handler to require three fingers if gesture handlin…
Oct 18, 2021
7399cac
remove removing override class while gesture handler still active
Oct 19, 2021
e1a12ac
fixed typos in unit test
Oct 19, 2021
8d3f030
Switched term from gestureHandling to cooperativeGestures
Oct 19, 2021
b7f0b63
forgot this name change
Oct 19, 2021
f5b2144
added clearTimeout to disable methods
Oct 19, 2021
686c5fc
addressed issue with css override class when touch zoom rotate is ena…
Oct 19, 2021
376da3e
changed override css class to pan-x pan-y
Oct 19, 2021
14395a0
add cooperative to comment
avpeery Oct 19, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions debug/scroll_zoom_blocker.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Scroll Zoom Blocker Control</title>
<title>Cooperative Gestures</title>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link rel='stylesheet' href='../dist/mapbox-gl.css' />
Expand All @@ -12,7 +12,7 @@
</head>

<body>
<div id='map' style='width: 250px; height: 250px;'></div>
<div id='map' style='width: 400px; height: 400px;'></div>

<div style='width: 500px; height: 500px;'></div>
<script src='../dist/mapbox-gl-dev.js'></script>
Expand All @@ -24,7 +24,7 @@
zoom: 12.5,
center: [-77.01866, 38.888],
style: 'mapbox://styles/mapbox/streets-v10',
gestureHandling: true
cooperativeGestures: true
});

map.addControl(new mapboxgl.FullscreenControl());
Expand Down
7 changes: 7 additions & 0 deletions src/css/mapbox-gl.css
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,7 @@ a.mapboxgl-ctrl-logo.mapboxgl-compact {
}
}

.mapboxgl-touch-pan-blocker,
.mapboxgl-scroll-zoom-blocker {
color: #fff;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
Expand All @@ -789,7 +790,13 @@ a.mapboxgl-ctrl-logo.mapboxgl-compact {
transition-delay: 1s;
}

.mapboxgl-touch-pan-blocker-show,
.mapboxgl-scroll-zoom-blocker-show {
opacity: 1;
transition: opacity 0.1s ease-in-out;
}

.mapboxgl-canvas-container.mapboxgl-touch-pan-blocker-override.mapboxgl-scrollable-page,
.mapboxgl-canvas-container.mapboxgl-touch-pan-blocker-override.mapboxgl-scrollable-page .mapboxgl-canvas {
touch-action: pan-x pan-y;
}
3 changes: 2 additions & 1 deletion src/ui/default_locale.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ const defaultLocale = {
'ScaleControl.Miles': 'mi',
'ScaleControl.NauticalMiles': 'nm',
'ScrollZoomBlocker.CtrlMessage': 'Use ctrl + scroll to zoom the map',
'ScrollZoomBlocker.CmdMessage': 'Use ⌘ + scroll to zoom the map'
'ScrollZoomBlocker.CmdMessage': 'Use ⌘ + scroll to zoom the map',
'TouchPanBlocker.Message': 'Use two fingers to move the map'
};

export default defaultLocale;
9 changes: 6 additions & 3 deletions src/ui/handler/scroll_zoom.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ class ScrollZoomHandler {
if (this.isEnabled()) return;
this._enabled = true;
this._aroundCenter = options && options.around === 'center';
if (this._map._gestureHandling) this._addScrollZoomBlocker();
if (this._map._cooperativeGestures) this._addScrollZoomBlocker();
}

/**
Expand All @@ -155,13 +155,16 @@ class ScrollZoomHandler {
disable() {
if (!this.isEnabled()) return;
this._enabled = false;
if (this._map._gestureHandling) this._alertContainer.remove();
if (this._map._cooperativeGestures) {
clearTimeout(this._alertTimer);
this._alertContainer.remove();
}
}

wheel(e: WheelEvent) {
if (!this.isEnabled()) return;

if (this._map._gestureHandling) {
if (this._map._cooperativeGestures) {
if (!e.ctrlKey && !e.metaKey && !this.isZooming() && !this._isFullscreen()) {
this._showBlockerAlert();
return;
Expand Down
60 changes: 59 additions & 1 deletion src/ui/handler/touch_pan.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
// @flow

import Point from '@mapbox/point-geometry';
import type Map from '../map.js';
import {indexTouches} from './handler_util.js';
import {bindAll} from '../../util/util.js';
import DOM from '../../util/dom.js';

export default class TouchPanHandler {

_map: Map;
_el: HTMLElement;
_enabled: boolean;
_active: boolean;
_touches: { [string | number]: Point };
_minTouches: number;
_clickTolerance: number;
_sum: Point;
_alertContainer: HTMLElement;
_alertTimer: TimeoutID;

constructor(options: { clickTolerance: number }) {
constructor(map: Map, options: { clickTolerance: number }) {
this._map = map;
this._el = map.getCanvasContainer();
this._minTouches = 1;
this._clickTolerance = options.clickTolerance || 1;
this.reset();
bindAll(['_addTouchPanBlocker', '_showTouchPanBlockerAlert'], this);
}

reset() {
Expand All @@ -30,7 +40,21 @@ export default class TouchPanHandler {

touchmove(e: TouchEvent, points: Array<Point>, mapTouches: Array<Touch>) {
if (!this._active || mapTouches.length < this._minTouches) return;

// if cooperative gesture handling is set to true, require two fingers to touch pan
if (this._map._cooperativeGestures && !this._map.isMoving()) {
if (mapTouches.length === 1) {
this._showTouchPanBlockerAlert();
return;
} else if (this._alertContainer.style.visibility !== 'hidden') {
// immediately hide alert if it is visible when two fingers are used to pan.
this._alertContainer.style.visibility = 'hidden';
clearTimeout(this._alertTimer);
}
}

e.preventDefault();

return this._calculateTransform(e, points, mapTouches);
}

Expand Down Expand Up @@ -84,10 +108,20 @@ export default class TouchPanHandler {

enable() {
this._enabled = true;
if (this._map._cooperativeGestures) {
this._addTouchPanBlocker();
// override touch-action css property to enable scrolling page over map
this._el.classList.add('mapboxgl-touch-pan-blocker-override', 'mapboxgl-scrollable-page');
}
}

disable() {
this._enabled = false;
avpeery marked this conversation as resolved.
Show resolved Hide resolved
if (this._map._cooperativeGestures) {
clearTimeout(this._alertTimer);
this._alertContainer.remove();
this._el.classList.remove('mapboxgl-touch-pan-blocker-override', 'mapboxgl-scrollable-page');
}
this.reset();
}

Expand All @@ -98,4 +132,28 @@ export default class TouchPanHandler {
isActive() {
return this._active;
}

_addTouchPanBlocker() {
if (this._map && !this._alertContainer) {
this._alertContainer = DOM.create('div', 'mapboxgl-touch-pan-blocker', this._map._container);

this._alertContainer.textContent = this._map._getUIString('TouchPanBlocker.Message');

// dynamically set the font size of the touch pan blocker alert message
this._alertContainer.style.fontSize = `${Math.max(10, Math.min(24, Math.floor(this._el.clientWidth * 0.05)))}px`;
}
}

_showTouchPanBlockerAlert() {
if (this._alertContainer.style.visibility === 'hidden') this._alertContainer.style.visibility = 'visible';

this._alertContainer.classList.add('mapboxgl-touch-pan-blocker-show');

clearTimeout(this._alertTimer);

this._alertTimer = setTimeout(() => {
this._alertContainer.classList.remove('mapboxgl-touch-pan-blocker-show');
}, 500);
}

}
11 changes: 11 additions & 0 deletions src/ui/handler/touch_zoom_rotate.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import Point from '@mapbox/point-geometry';
import DOM from '../../util/dom.js';
import type Map from '../map.js';

class TwoTouchHandler {

Expand Down Expand Up @@ -205,6 +206,12 @@ export class TouchPitchHandler extends TwoTouchHandler {
_valid: boolean | void;
_firstMove: number;
_lastPoints: [Point, Point];
_map: Map;

constructor(map: Map) {
super();
this._map = map;
}

reset() {
super.reset();
Expand All @@ -220,13 +227,17 @@ export class TouchPitchHandler extends TwoTouchHandler {
this._valid = false;

}

}

_move(points: [Point, Point], center: Point, e: TouchEvent) {
const vectorA = points[0].sub(this._lastPoints[0]);
const vectorB = points[1].sub(this._lastPoints[1]);

if (this._map._cooperativeGestures && e.touches.length < 3) return;

this._valid = this.gestureBeginsVertically(vectorA, vectorB, e.timeStamp);

if (!this._valid) return;

this._lastPoints = points;
Expand Down
5 changes: 3 additions & 2 deletions src/ui/handler_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ class HandlerManager {
const tapDragZoom = new TapDragZoomHandler();
this._add('tapDragZoom', tapDragZoom);

const touchPitch = map.touchPitch = new TouchPitchHandler();
const touchPitch = map.touchPitch = new TouchPitchHandler(map);
this._add('touchPitch', touchPitch);

const mouseRotate = new MouseRotateHandler(options);
Expand All @@ -256,7 +256,7 @@ class HandlerManager {
this._add('mousePitch', mousePitch, ['mouseRotate']);

const mousePan = new MousePanHandler(options);
const touchPan = new TouchPanHandler(options);
const touchPan = new TouchPanHandler(map, options);
map.dragPan = new DragPanHandler(el, mousePan, touchPan);
this._add('mousePan', mousePan);
this._add('touchPan', touchPan, ['touchZoom', 'touchRotate']);
Expand Down Expand Up @@ -309,6 +309,7 @@ class HandlerManager {
isZooming() {
return !!this._eventsInProgress.zoom || this._map.scrollZoom.isZooming();
}

isRotating() {
return !!this._eventsInProgress.rotate;
}
Expand Down
10 changes: 5 additions & 5 deletions src/ui/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ type MapOptions = {
doubleClickZoom?: boolean,
touchZoomRotate?: boolean,
touchPitch?: boolean,
gestureHandling?: boolean,
cooperativeGestures?: boolean,
trackResize?: boolean,
center?: LngLatLike,
zoom?: number,
Expand Down Expand Up @@ -149,7 +149,7 @@ const defaultOptions = {
doubleClickZoom: true,
touchZoomRotate: true,
touchPitch: true,
gestureHandling: false,
cooperativeGestures: false,

bearingSnap: 7,
clickTolerance: 3,
Expand Down Expand Up @@ -236,7 +236,7 @@ const defaultOptions = {
* @param {boolean} [options.doubleClickZoom=true] If `true`, the "double click to zoom" interaction is enabled (see {@link DoubleClickZoomHandler}).
* @param {boolean | Object} [options.touchZoomRotate=true] If `true`, the "pinch to rotate and zoom" interaction is enabled. An `Object` value is passed as options to {@link TouchZoomRotateHandler#enable}.
* @param {boolean | Object} [options.touchPitch=true] If `true`, the "drag to pitch" interaction is enabled. An `Object` value is passed as options to {@link TouchPitchHandler#enable}.
* @param {boolean} [options.gestureHandling=false] If `true`, scroll zoom will require pressing the ctrl or ⌘ key while scrolling to zoom map.
* @param {boolean} [options.cooperativeGestures] If `true`, scroll zoom will require pressing the ctrl or ⌘ key while scrolling to zoom map, and touch pan will require using two fingers while panning to move the map. Touch pitch will require three fingers to activate if enabled.
* @param {boolean} [options.trackResize=true] If `true`, the map will automatically resize when the browser window resizes.
* @param {LngLatLike} [options.center=[0, 0]] The inital geographical centerpoint of the map. If `center` is not specified in the constructor options, Mapbox GL JS will look for it in the map's style object. If it is not specified in the style, either, it will default to `[0, 0]` Note: Mapbox GL uses longitude, latitude coordinate order (as opposed to latitude, longitude) to match GeoJSON.
* @param {number} [options.zoom=0] The initial zoom level of the map. If `zoom` is not specified in the constructor options, Mapbox GL JS will look for it in the map's style object. If it is not specified in the style, either, it will default to `0`.
Expand Down Expand Up @@ -345,7 +345,7 @@ class Map extends Camera {
_removed: boolean;
_speedIndexTiming: boolean;
_clickTolerance: number;
_gestureHandling: boolean;
_cooperativeGestures: boolean;
_silenceAuthErrors: boolean;
_averageElevationLastSampledAt: number;
_averageElevation: EasedVariable;
Expand Down Expand Up @@ -446,7 +446,7 @@ class Map extends Camera {
this._mapId = uniqueId();
this._locale = extend({}, defaultLocale, options.locale);
this._clickTolerance = options.clickTolerance;
this._gestureHandling = options.gestureHandling;
this._cooperativeGestures = options.cooperativeGestures;

this._averageElevationLastSampledAt = -Infinity;
this._averageElevation = new EasedVariable(0);
Expand Down
1 change: 1 addition & 0 deletions test/release/scroll_zoom_blocker.html
22 changes: 11 additions & 11 deletions test/unit/ui/handler/scroll_zoom.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ function createMap(t) {
});
}

function createMapWithGestureHandling(t) {
function createMapWithCooperativeGestures(t) {
t.stub(Map.prototype, '_detectMissingCSS');
t.stub(Map.prototype, '_authenticate');
return new Map({
container: DOM.create('div', '', window.document.body),
gestureHandling: true
cooperativeGestures: true
});
}

Expand Down Expand Up @@ -376,15 +376,15 @@ test('ScrollZoomHandler', (t) => {
t.end();
});

test('When gestureHandling option is set to true, a .mapboxgl-scroll-zoom-blocker element is added to map', (t) => {
const map = createMapWithGestureHandling(t);
test('When cooperativeGestures option is set to true, a .mapboxgl-scroll-zoom-blocker element is added to map', (t) => {
const map = createMapWithCooperativeGestures(t);

t.equal(map.getContainer().querySelectorAll('.mapboxgl-scroll-zoom-blocker').length, 1);
t.end();
});

test('When gestureHandling option is set to true, scroll zoom is prevented when the ctrl key or meta key is not pressed during wheel event', (t) => {
const map = createMapWithGestureHandling(t);
test('When cooperativeGestures option is set to true, scroll zoom is prevented when the ctrl key or meta key is not pressed during wheel event', (t) => {
const map = createMapWithCooperativeGestures(t);

const zoomSpy = t.spy();
map.on('zoom', zoomSpy);
Expand All @@ -395,8 +395,8 @@ test('When gestureHandling option is set to true, scroll zoom is prevented when
t.end();
});

test('When gestureHandling option is set to true, scroll zoom is activated when ctrl key is pressed during wheel event', (t) => {
const map = createMapWithGestureHandling(t);
test('When cooperativeGestures option is set to true, scroll zoom is activated when ctrl key is pressed during wheel event', (t) => {
const map = createMapWithCooperativeGestures(t);

const zoomSpy = t.spy();
map.on('zoom', zoomSpy);
Expand All @@ -409,8 +409,8 @@ test('When gestureHandling option is set to true, scroll zoom is activated when
t.end();
});

test('When gestureHandling option is set to true, scroll zoom is activated when meta key is pressed during wheel event', (t) => {
const map = createMapWithGestureHandling(t);
test('When cooperativeGestures option is set to true, scroll zoom is activated when meta key is pressed during wheel event', (t) => {
const map = createMapWithCooperativeGestures(t);

const zoomSpy = t.spy();
map.on('zoom', zoomSpy);
Expand All @@ -424,7 +424,7 @@ test('When gestureHandling option is set to true, scroll zoom is activated when
});

test('Disabling scrollZoom removes scroll zoom blocker container', (t) => {
const map = createMapWithGestureHandling(t);
const map = createMapWithCooperativeGestures(t);

map.scrollZoom.disable();

Expand Down
Loading