panzoom
Version:
Extensible, mobile friendly pan and zoom framework (supports DOM and SVG).
136 lines (102 loc) • 3.19 kB
JavaScript
/**
* Allows smooth kinetic scrolling of the surface
*/
module.exports = kinetic;
function kinetic(getPoint, scroll, settings) {
if (typeof settings !== 'object') {
// setting could come as boolean, we should ignore it, and use an object.
settings = {};
}
var minVelocity = typeof settings.minVelocity === 'number' ? settings.minVelocity : 5;
var amplitude = typeof settings.amplitude === 'number' ? settings.amplitude : 0.25;
var cancelAnimationFrame = typeof settings.cancelAnimationFrame === 'function' ? settings.cancelAnimationFrame : getCancelAnimationFrame();
var requestAnimationFrame = typeof settings.requestAnimationFrame === 'function' ? settings.requestAnimationFrame : getRequestAnimationFrame();
var lastPoint;
var timestamp;
var timeConstant = 342;
var ticker;
var vx, targetX, ax;
var vy, targetY, ay;
var raf;
return {
start: start,
stop: stop,
cancel: dispose
};
function dispose() {
cancelAnimationFrame(ticker);
cancelAnimationFrame(raf);
}
function start() {
lastPoint = getPoint();
ax = ay = vx = vy = 0;
timestamp = new Date();
cancelAnimationFrame(ticker);
cancelAnimationFrame(raf);
// we start polling the point position to accumulate velocity
// Once we stop(), we will use accumulated velocity to keep scrolling
// an object.
ticker = requestAnimationFrame(track);
}
function track() {
var now = Date.now();
var elapsed = now - timestamp;
timestamp = now;
var currentPoint = getPoint();
var dx = currentPoint.x - lastPoint.x;
var dy = currentPoint.y - lastPoint.y;
lastPoint = currentPoint;
var dt = 1000 / (1 + elapsed);
// moving average
vx = 0.8 * dx * dt + 0.2 * vx;
vy = 0.8 * dy * dt + 0.2 * vy;
ticker = requestAnimationFrame(track);
}
function stop() {
cancelAnimationFrame(ticker);
cancelAnimationFrame(raf);
var currentPoint = getPoint();
targetX = currentPoint.x;
targetY = currentPoint.y;
timestamp = Date.now();
if (vx < -minVelocity || vx > minVelocity) {
ax = amplitude * vx;
targetX += ax;
}
if (vy < -minVelocity || vy > minVelocity) {
ay = amplitude * vy;
targetY += ay;
}
raf = requestAnimationFrame(autoScroll);
}
function autoScroll() {
var elapsed = Date.now() - timestamp;
var moving = false;
var dx = 0;
var dy = 0;
if (ax) {
dx = -ax * Math.exp(-elapsed / timeConstant);
if (dx > 0.5 || dx < -0.5) moving = true;
else dx = ax = 0;
}
if (ay) {
dy = -ay * Math.exp(-elapsed / timeConstant);
if (dy > 0.5 || dy < -0.5) moving = true;
else dy = ay = 0;
}
if (moving) {
scroll(targetX + dx, targetY + dy);
raf = requestAnimationFrame(autoScroll);
}
}
}
function getCancelAnimationFrame() {
if (typeof cancelAnimationFrame === 'function') return cancelAnimationFrame;
return clearTimeout;
}
function getRequestAnimationFrame() {
if (typeof requestAnimationFrame === 'function') return requestAnimationFrame;
return function (handler) {
return setTimeout(handler, 16);
};
}