UNPKG

panzoom

Version:

Extensible, mobile friendly pan and zoom framework (supports DOM and SVG).

136 lines (102 loc) 3.19 kB
/** * 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); }; }