diff --git a/src/ui/bind_handlers.js b/src/ui/bind_handlers.js index af3192b5f39..c020e35efec 100644 --- a/src/ui/bind_handlers.js +++ b/src/ui/bind_handlers.js @@ -1,6 +1,10 @@ // @flow -const {MapMouseEvent, MapTouchEvent} = require('../ui/events'); +const { + MapMouseEvent, + MapTouchEvent, + MapWheelEvent +} = require('../ui/events'); import type Map from './map'; @@ -38,6 +42,7 @@ module.exports = function bindHandlers(map: Map, options: {}) { el.addEventListener('click', onClick, false); el.addEventListener('dblclick', onDblClick, false); el.addEventListener('contextmenu', onContextMenu, false); + el.addEventListener('wheel', onWheel, false); function onMouseDown(e: MouseEvent) { mouseDown = true; @@ -158,4 +163,15 @@ module.exports = function bindHandlers(map: Map, options: {}) { e.preventDefault(); } + + function onWheel(e: WheelEvent) { + const mapEvent = new MapWheelEvent('wheel', map, e); + map.fire(mapEvent); + + if (mapEvent.defaultPrevented) { + return; + } + + map.scrollZoom.onWheel(e); + } }; diff --git a/src/ui/events.js b/src/ui/events.js index b3b1793117e..82b4127dd95 100644 --- a/src/ui/events.js +++ b/src/ui/events.js @@ -166,6 +166,54 @@ class MapTouchEvent extends Event { } } + +/** + * `MapWheelEvent` is the event type for the `wheel` map event. + * @extends {Object} + */ +class MapWheelEvent extends Event { + /** + * The event type. + */ + type: 'wheel'; + + /** + * The `Map` object that fired the event. + */ + map: Map; + + /** + * The DOM event which caused the map event. + */ + originalEvent: WheelEvent; + + /** + * Prevents subsequent default processing of the event by the map. + * + * Calling this method will prevent the the behavior of {@link ScrollZoomHandler}. + */ + preventDefault() { + this._defaultPrevented = true; + } + + /** + * `true` if `preventDefault` has been called. + */ + get defaultPrevented(): boolean { + return this._defaultPrevented; + } + + _defaultPrevented: boolean; + + /** + * @private + */ + constructor(type: string, map: Map, originalEvent: WheelEvent) { + super(type, { originalEvent }); + this._defaultPrevented = false; + } +} + /** * @typedef {Object} MapBoxZoomEvent * @property {MouseEvent} originalEvent @@ -339,6 +387,16 @@ export type MapEvent = */ | 'contextmenu' + /** + * Fired when a [`wheel`](https://developer.mozilla.org/en-US/docs/Web/Events/wheel) event occurs within the map. + * + * @event wheel + * @memberof Map + * @instance + * @property {MapWheelEvent} data + */ + | 'wheel' + /** * Fired when a [`touchstart`](https://developer.mozilla.org/en-US/docs/Web/Events/touchstart) event occurs within the map. * @@ -729,5 +787,6 @@ export type MapEvent = module.exports = { MapMouseEvent, - MapTouchEvent + MapTouchEvent, + MapWheelEvent }; diff --git a/src/ui/handler/scroll_zoom.js b/src/ui/handler/scroll_zoom.js index 3d8dc276d88..3143a8caa2f 100644 --- a/src/ui/handler/scroll_zoom.js +++ b/src/ui/handler/scroll_zoom.js @@ -23,9 +23,7 @@ const wheelZoomRate = 1 / 450; // is used to limit zoom rate in the case of very fast scrolling const maxScalePerFrame = 2; -const ua = window.navigator.userAgent.toLowerCase(), - firefox = ua.indexOf('firefox') !== -1, - safari = ua.indexOf('safari') !== -1 && ua.indexOf('chrom') === -1; +const firefox = window.navigator.userAgent.toLowerCase().indexOf('firefox') !== -1; /** * The `ScrollZoomHandler` allows the user to zoom the map by scrolling. @@ -95,8 +93,6 @@ class ScrollZoomHandler { */ enable(options: any) { if (this.isEnabled()) return; - this._el.addEventListener('wheel', this._onWheel, false); - this._el.addEventListener('mousewheel', this._onWheel, false); this._enabled = true; this._aroundCenter = options && options.around === 'center'; } @@ -109,24 +105,21 @@ class ScrollZoomHandler { */ disable() { if (!this.isEnabled()) return; - this._el.removeEventListener('wheel', this._onWheel); - this._el.removeEventListener('mousewheel', this._onWheel); this._enabled = false; } - _onWheel(e: any) { - let value = 0; + onWheel(e: WheelEvent) { + if (!this.isEnabled()) return; - if (e.type === 'wheel') { - value = e.deltaY; - // Firefox doubles the values on retina screens... - // Remove `any` casts when https://github.com/facebook/flow/issues/4879 is fixed. - if (firefox && e.deltaMode === (window.WheelEvent: any).DOM_DELTA_PIXEL) value /= browser.devicePixelRatio; - if (e.deltaMode === (window.WheelEvent: any).DOM_DELTA_LINE) value *= 40; + let value = e.deltaY; - } else if (e.type === 'mousewheel') { - value = -e.wheelDeltaY; - if (safari) value = value / 3; + // Firefox doubles the values on retina screens... + // Remove `any` casts when https://github.com/facebook/flow/issues/4879 is fixed. + if (firefox && e.deltaMode === (window.WheelEvent: any).DOM_DELTA_PIXEL) { + value /= browser.devicePixelRatio; + } + if (e.deltaMode === (window.WheelEvent: any).DOM_DELTA_LINE) { + value *= 40; } const now = browser.now(), diff --git a/test/unit/ui/handler/scroll_zoom.test.js b/test/unit/ui/handler/scroll_zoom.test.js index 45029189ff2..d29872e3d2b 100644 --- a/test/unit/ui/handler/scroll_zoom.test.js +++ b/test/unit/ui/handler/scroll_zoom.test.js @@ -19,7 +19,7 @@ function createMap(options) { }, options)); } -test('ScrollZoomHandler zooms in response to wheel events', (t) => { +test('ScrollZoomHandler', (t) => { const browserNow = t.stub(browser, 'now'); let now = 1555555555555; browserNow.callsFake(() => now); @@ -115,6 +115,22 @@ test('ScrollZoomHandler zooms in response to wheel events', (t) => { t.end(); }); + test('does not zoom if preventDefault is called on the wheel event', (t) => { + const map = createMap(); + + map.on('wheel', e => e.preventDefault()); + + simulate.wheel(map.getCanvas(), {type: 'wheel', deltaY: -simulate.magicWheelZoomDelta}); + map._updateCamera(); + + now += 400; + map._updateCamera(); + + t.equal(map.getZoom(), 0); + + map.remove(); + t.end(); + }); + t.end(); }); -