Skip to content

Commit

Permalink
Refactor drag pan handler to use camera animation
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
Anand Thakker committed Jan 17, 2018
1 parent 26b1462 commit b1be8aa
Showing 1 changed file with 71 additions and 62 deletions.
133 changes: 71 additions & 62 deletions src/ui/handler/drag_pan.js
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit b1be8aa

Please sign in to comment.