From b1be8aa4936a446dfb1fa9cf7319786673a05aa0 Mon Sep 17 00:00:00 2001 From: Anand Thakker Date: Tue, 16 Jan 2018 21:40:43 -0500 Subject: [PATCH] Refactor drag pan handler to use camera animation Instead of updating the transform directly within the mousemove handler, we cede control to the render loop by doing our transform updates in the callback we pass to `Camera#_startAnimation`. This way, we synchronously update the transform, render the map, and fire the `move` event (and thus trigger any listeners that might perform DOM updates). --- src/ui/handler/drag_pan.js | 133 ++++++++++++++++++++----------------- 1 file changed, 71 insertions(+), 62 deletions(-) diff --git a/src/ui/handler/drag_pan.js b/src/ui/handler/drag_pan.js index 3090c239296..a27d283c7a6 100644 --- a/src/ui/handler/drag_pan.js +++ b/src/ui/handler/drag_pan.js @@ -7,6 +7,7 @@ const browser = require('../../util/browser'); import type Map from '../map'; import type Point from '@mapbox/point-geometry'; +import type Transform from '../../geo/transform'; const inertiaLinearity = 0.3, inertiaEasing = util.bezier(0, 0, inertiaLinearity, 1), @@ -25,8 +26,10 @@ class DragPanHandler { _enabled: boolean; _active: boolean; _pos: Point; + _previousPos: ?Point; _startPos: Point; _inertia: Array<[number, Point]>; + _lastMoveEvent: MouseEvent | TouchEvent | void; constructor(map: Map) { this._map = map; @@ -108,76 +111,82 @@ class DragPanHandler { _onMove(e: MouseEvent | TouchEvent) { if (this._ignoreEvent(e)) return; - - if (!this.isActive()) { - this._active = true; - this._map.moving = true; - this._fireEvent('dragstart', e); - this._fireEvent('movestart', e); - } - - const pos = DOM.mousePos(this._el, e), - map = this._map; - - map.stop(); - this._drainInertiaBuffer(); - this._inertia.push([browser.now(), pos]); - - map.transform.setLocationAtPoint(map.transform.pointLocation(this._pos), pos); - - this._fireEvent('drag', e); - this._fireEvent('move', e); - - this._pos = pos; - + this._lastMoveEvent = e; e.preventDefault(); - } - - _onUp(e: MouseEvent | TouchEvent | FocusEvent) { - if (!this.isActive()) return; - this._active = false; - this._fireEvent('dragend', e); + this._pos = DOM.mousePos(this._el, e); this._drainInertiaBuffer(); + this._inertia.push([browser.now(), this._pos]); - const finish = () => { - this._map.moving = false; - this._fireEvent('moveend', e); - }; - - const inertia = this._inertia; - if (inertia.length < 2) { - finish(); - return; - } - - const last = inertia[inertia.length - 1], - first = inertia[0], - flingOffset = last[1].sub(first[1]), - flingDuration = (last[0] - first[0]) / 1000; - - if (flingDuration === 0 || last[1].equals(first[1])) { - finish(); + if (this.isActive()) { return; } - // calculate px/s velocity & adjust for increased initial animation speed when easing out - const velocity = flingOffset.mult(inertiaLinearity / flingDuration); - let speed = velocity.mag(); // px/s - - if (speed > inertiaMaxSpeed) { - speed = inertiaMaxSpeed; - velocity._unit()._mult(speed); - } - - const duration = speed / (inertiaDeceleration * inertiaLinearity), - offset = velocity.mult(-duration / 2); + this._active = true; + this._map.moving = true; + this._fireEvent('dragstart', e); + this._fireEvent('movestart', e); + + this._map._startAnimation((tr: Transform) => { + const e = this._lastMoveEvent; + if (!e) return; + if (this._previousPos) { + tr.setLocationAtPoint(tr.pointLocation(this._previousPos), this._pos); + this._fireEvent('drag', e); + this._fireEvent('move', e); + } + delete this._lastMoveEvent; + this._previousPos = this._pos; + }, () => { + this._active = false; + delete this._lastMoveEvent; + this._fireEvent('dragend', e); + this._drainInertiaBuffer(); + + const finish = () => { + this._map.moving = false; + this._fireEvent('moveend', e); + }; + + const inertia = this._inertia; + if (inertia.length < 2) { + finish(); + return; + } + + const last = inertia[inertia.length - 1], + first = inertia[0], + flingOffset = last[1].sub(first[1]), + flingDuration = (last[0] - first[0]) / 1000; + + if (flingDuration === 0 || last[1].equals(first[1])) { + finish(); + return; + } + + // calculate px/s velocity & adjust for increased initial animation speed when easing out + const velocity = flingOffset.mult(inertiaLinearity / flingDuration); + let speed = velocity.mag(); // px/s + + if (speed > inertiaMaxSpeed) { + speed = inertiaMaxSpeed; + velocity._unit()._mult(speed); + } + + const duration = speed / (inertiaDeceleration * inertiaLinearity), + offset = velocity.mult(-duration / 2); + + this._map.panBy(offset, { + duration: duration * 1000, + easing: inertiaEasing, + noMoveStart: true + }, { originalEvent: e }); + }); + } - this._map.panBy(offset, { - duration: duration * 1000, - easing: inertiaEasing, - noMoveStart: true - }, { originalEvent: e }); + _onUp(e: MouseEvent | TouchEvent | FocusEvent) { + if (!this.isActive()) return; + this._map.stop(); } _onMouseUp(e: MouseEvent | FocusEvent) {