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 gestureHandling map option to implement scroll zoom blocker #11029

Merged
merged 23 commits into from Oct 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b16d8c1
WIP: implementing map option scroll zoom blocker
Sep 17, 2021
f2c5064
fixed lint issues
Sep 17, 2021
eb039ca
fixed issue with repeating wheel events
Sep 17, 2021
030b1d3
fixed unit tests fail by checking if container exists
Sep 17, 2021
14b71a9
added in locale string for alert message
Sep 17, 2021
28c179f
add transition effect without using setTimeout, issue with Flow
Sep 20, 2021
ca4677d
changes requests for CSS and removal of getElement method
Sep 21, 2021
6bd9658
changed alpha in css background to .7 for better readability
Sep 21, 2021
fcb56ca
removed binded methods that were deleted
Sep 21, 2021
2ae06a6
changed ontransitionend to addEventListener('transitionend') to fix f…
Sep 21, 2021
cd7384c
added some unit tests, added ability to use meta key, transition not …
Sep 24, 2021
480e2de
reverted to setTimeout, transitionend event listener does not work in…
Sep 24, 2021
3ccfc6f
fixed lint issue
Sep 24, 2021
286c957
renamed this._container for clarity
Sep 24, 2021
be393b9
added use to alert messages
Sep 24, 2021
2624e4a
dynamically set font size on adding scroll zoom blocker alert to map
Sep 27, 2021
d52d26b
removed comment in css
Sep 27, 2021
9940254
changed max font from 30 to 24
Sep 27, 2021
e594cc6
prevent scroll zoom blocker in the case that the map is full screen
Sep 28, 2021
57bef20
cleaned up the debug html
Sep 28, 2021
c4de959
changed method name to isFullscreen, bumped up dynamically set font size
Sep 29, 2021
bab2baa
replaced requireCtrl scrollZoom option with gestureHandling map option
Oct 1, 2021
6a7adff
set isFullscreen to check window fullscreen instead of control, and s…
Oct 1, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 34 additions & 0 deletions debug/scroll_zoom_blocker.html
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<title>Scroll Zoom Blocker Control</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' />
<style>
body { margin: 0; padding: 0; }
html, body, #map { height: 100%; }
</style>
</head>

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

<div style='width: 500px; height: 500px;'></div>
<script src='../dist/mapbox-gl-dev.js'></script>
<script src='../debug/access_token_generated.js'></script>
<script>

var map = window.map = new mapboxgl.Map({
container: 'map',
zoom: 12.5,
center: [-77.01866, 38.888],
style: 'mapbox://styles/mapbox/streets-v10',
gestureHandling: true
});

map.addControl(new mapboxgl.FullscreenControl());

</script>
</body>
</html>
24 changes: 24 additions & 0 deletions src/css/mapbox-gl.css
Expand Up @@ -769,3 +769,27 @@ a.mapboxgl-ctrl-logo.mapboxgl-compact {
display: none;
}
}

.mapboxgl-scroll-zoom-blocker {
color: #fff;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
justify-content: center;
text-align: center;
position: absolute;
display: flex;
align-items: center;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
opacity: 0;
pointer-events: none;
transition: opacity 0.75s ease-in-out;
transition-delay: 1s;
}

.mapboxgl-scroll-zoom-blocker-show {
opacity: 1;
transition: opacity 0.1s ease-in-out;
}
5 changes: 3 additions & 2 deletions src/ui/default_locale.js
Expand Up @@ -15,8 +15,9 @@ const defaultLocale = {
'ScaleControl.Meters': 'm',
'ScaleControl.Kilometers': 'km',
'ScaleControl.Miles': 'mi',
'ScaleControl.NauticalMiles': 'nm'

'ScaleControl.NauticalMiles': 'nm',
'ScrollZoomBlocker.CtrlMessage': 'Use ctrl + scroll to zoom the map',
'ScrollZoomBlocker.CmdMessage': 'Use ⌘ + scroll to zoom the map'
};

export default defaultLocale;
51 changes: 50 additions & 1 deletion src/ui/handler/scroll_zoom.js
Expand Up @@ -60,6 +60,9 @@ class ScrollZoomHandler {
_defaultZoomRate: number;
_wheelZoomRate: number;

_alertContainer: HTMLElement; // used to display the scroll zoom blocker alert
_alertTimer: TimeoutID;

/**
* @private
*/
Expand All @@ -73,7 +76,8 @@ class ScrollZoomHandler {
this._defaultZoomRate = defaultZoomRate;
this._wheelZoomRate = wheelZoomRate;

bindAll(['_onTimeout'], this);
bindAll(['_onTimeout', '_addScrollZoomBlocker', '_showBlockerAlert', '_isFullscreen'], this);

}

/**
Expand Down Expand Up @@ -139,6 +143,7 @@ class ScrollZoomHandler {
if (this.isEnabled()) return;
this._enabled = true;
this._aroundCenter = options && options.around === 'center';
if (this._map._gestureHandling) this._addScrollZoomBlocker();
}

/**
Expand All @@ -150,11 +155,23 @@ class ScrollZoomHandler {
disable() {
if (!this.isEnabled()) return;
this._enabled = false;
if (this._map._gestureHandling) this._alertContainer.remove();
}

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

if (this._map._gestureHandling) {
if (!e.ctrlKey && !e.metaKey && !this.isZooming() && !this._isFullscreen()) {
this._showBlockerAlert();
return;
} else if (this._alertContainer.style.visibility !== 'hidden') {
// immediately hide alert if it is visible when ctrl or ⌘ is pressed while scroll zooming.
this._alertContainer.style.visibility = 'hidden';
clearTimeout(this._alertTimer);
}
}

// Remove `any` cast when https://github.com/facebook/flow/issues/4879 is fixed.
let value = e.deltaMode === (window.WheelEvent: any).DOM_DELTA_LINE ? e.deltaY * 40 : e.deltaY;
const now = browser.now(),
Expand Down Expand Up @@ -248,6 +265,7 @@ class ScrollZoomHandler {
this._frameId = null;

if (!this.isActive()) return;

const tr = this._map.transform;

const startingZoom = () => {
Expand Down Expand Up @@ -356,6 +374,37 @@ class ScrollZoomHandler {
reset() {
this._active = false;
}

_addScrollZoomBlocker() {
if (this._map && !this._alertContainer) {
this._alertContainer = DOM.create('div', 'mapboxgl-scroll-zoom-blocker', this._map._container);

if (/(Mac|iPad)/i.test(window.navigator.userAgent)) {
this._alertContainer.textContent = this._map._getUIString('ScrollZoomBlocker.CmdMessage');
} else {
this._alertContainer.textContent = this._map._getUIString('ScrollZoomBlocker.CtrlMessage');
}

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

_isFullscreen() {
return window.document.fullscreenElement !== null;
}
avpeery marked this conversation as resolved.
Show resolved Hide resolved

_showBlockerAlert() {
if (this._alertContainer.style.visibility === 'hidden') this._alertContainer.style.visibility = 'visible';
this._alertContainer.classList.add('mapboxgl-scroll-zoom-blocker-show');

clearTimeout(this._alertTimer);

this._alertTimer = setTimeout(() => {
this._alertContainer.classList.remove('mapboxgl-scroll-zoom-blocker-show');
}, 200);
avpeery marked this conversation as resolved.
Show resolved Hide resolved
}

}

export default ScrollZoomHandler;
5 changes: 5 additions & 0 deletions src/ui/map.js
Expand Up @@ -106,6 +106,7 @@ type MapOptions = {
doubleClickZoom?: boolean,
touchZoomRotate?: boolean,
touchPitch?: boolean,
gestureHandling?: boolean,
trackResize?: boolean,
center?: LngLatLike,
zoom?: number,
Expand Down Expand Up @@ -148,6 +149,7 @@ const defaultOptions = {
doubleClickZoom: true,
touchZoomRotate: true,
touchPitch: true,
gestureHandling: false,

bearingSnap: 7,
clickTolerance: 3,
Expand Down Expand Up @@ -234,6 +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.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 @@ -342,6 +345,7 @@ class Map extends Camera {
_removed: boolean;
_speedIndexTiming: boolean;
_clickTolerance: number;
_gestureHandling: boolean;
_silenceAuthErrors: boolean;
_averageElevationLastSampledAt: number;
_averageElevation: EasedVariable;
Expand Down Expand Up @@ -442,6 +446,7 @@ class Map extends Camera {
this._mapId = uniqueId();
this._locale = extend({}, defaultLocale, options.locale);
this._clickTolerance = options.clickTolerance;
this._gestureHandling = options.gestureHandling;

this._averageElevationLastSampledAt = -Infinity;
this._averageElevation = new EasedVariable(0);
Expand Down
65 changes: 65 additions & 0 deletions test/unit/ui/handler/scroll_zoom.test.js
Expand Up @@ -24,6 +24,15 @@ function createMap(t) {
});
}

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

test('ScrollZoomHandler', (t) => {
const browserNow = t.stub(browser, 'now');
let now = 1555555555555;
Expand Down Expand Up @@ -366,3 +375,59 @@ 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);

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

const zoomSpy = t.spy();
map.on('zoom', zoomSpy);

simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta});

t.equal(zoomSpy.callCount, 0);
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);

const zoomSpy = t.spy();
map.on('zoom', zoomSpy);

simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta, ctrlKey: true});

map._renderTaskQueue.run();

t.equal(zoomSpy.callCount, 1);
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);

const zoomSpy = t.spy();
map.on('zoom', zoomSpy);

simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta, metaKey: true});

map._renderTaskQueue.run();

t.equal(zoomSpy.callCount, 1);
t.end();
});

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

map.scrollZoom.disable();

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