react-flexigrid
Version:
A React table component designed to allow presenting millions of rows of data.
208 lines (165 loc) • 6.29 kB
JavaScript
import _classCallCheck from 'babel-runtime/helpers/classCallCheck';
import { requestAnimationFrame, getJudgeFunction } from '../utils';
var MOVE_AMPLITUDE = 1.6;
var DECELERATION_AMPLITUDE = 1.6;
var DECELERATION_FACTOR = 325;
var TRACKER_TIMEOUT = 100;
var TouchHandler = function () {
function TouchHandler(onTouchScroll, handleScrollX, handleScrollY, stopPropagation) {
var _this = this;
_classCallCheck(this, TouchHandler);
this.didTouchMove = function () {
// Fire scroll callback based on computed drag delta.
// Also track accummulated delta so we can calculate velocity
_this.dragAnimationId = null;
_this.onTouchScrollCallback(_this.deltaX, _this.deltaY);
_this.accumulatedDeltaX += _this.deltaX;
_this.accumulatedDeltaY += _this.deltaY;
_this.deltaX = 0;
_this.deltaY = 0;
};
this.track = function () {
// Compute velocity based on a weighted average of drag over
// last 100ms and previous velocity. Combining into a moving average
// results in a smoother scroll.
var now = Date.now();
var elapsed = now - _this.lastFrameTimestamp;
var oldVelocityX = _this.velocityX;
var oldVelocityY = _this.velocityY;
// We compute velocity using a weighted average of the current
// velocity and the previous velocity. If the previous velocity
// is 0, put the full weight on the last 100ms
var weight = 0.8;
if (elapsed < TRACKER_TIMEOUT) {
weight *= elapsed / TRACKER_TIMEOUT;
}
if (oldVelocityX === 0 && oldVelocityY === 0) {
weight = 1;
}
// Formula for computing weighted average of velocity
_this.velocityX = weight * (TRACKER_TIMEOUT * _this.accumulatedDeltaX / (1 + elapsed));
if (weight < 1) {
_this.velocityX += (1 - weight) * oldVelocityX;
}
_this.velocityY = weight * (TRACKER_TIMEOUT * _this.accumulatedDeltaY / (1 + elapsed));
if (weight < 1) {
_this.velocityY += (1 - weight) * oldVelocityY;
}
_this.accumulatedDeltaX = 0;
_this.accumulatedDeltaY = 0;
_this.lastFrameTimestamp = now;
};
this.startAutoScroll = function () {
// To kick off deceleration / momentum scrolling, handle any
// scrolling from a drag which was waiting for an animation
// frame. Then update our velocity.
// Finally start the momentum scrolling handler (autoScroll)
_this.autoScrollTimestamp = Date.now();
if (_this.deltaX > 0 || _this.deltaY > 0) {
_this.didTouchMove();
}
_this.track();
_this.autoScroll();
};
this.autoScroll = function () {
// Compute a scroll delta with an exponential decay based on
// time elapsed since drag was released. This is called
// recursively on animation frames until the delta is below
// a threshold (5 pixels)
var elapsed = Date.now() - _this.autoScrollTimestamp;
var factor = DECELERATION_AMPLITUDE * Math.exp(-elapsed / DECELERATION_FACTOR);
var deltaX = factor * _this.velocityX;
var deltaY = factor * _this.velocityY;
if (Math.abs(deltaX) <= 5 || !_this.handleScrollX(deltaX, deltaY)) {
deltaX = 0;
}
if (Math.abs(deltaY) <= 5 || !_this.handleScrollY(deltaY, deltaX)) {
deltaY = 0;
}
if (deltaX !== 0 || deltaY !== 0) {
_this.onTouchScrollCallback(deltaX, deltaY);
requestAnimationFrame(_this.autoScroll);
}
};
this.trackerId = null;
this.dragAnimationId = null;
this.deltaX = 0;
this.deltaY = 0;
this.lastTouchX = 0;
this.lastTouchY = 0;
this.velocityX = 0;
this.velocityY = 0;
this.accumulatedDeltaX = 0;
this.accumulatedDeltaY = 0;
this.lastFrameTimestamp = Date.now();
this.autoScrollTimestamp = Date.now();
this.handleScrollX = getJudgeFunction(handleScrollX);
this.handleScrollY = getJudgeFunction(handleScrollY);
this.stopPropagation = getJudgeFunction(stopPropagation);
this.onTouchScrollCallback = onTouchScroll;
}
TouchHandler.prototype.onTouchStart = function onTouchStart(e) {
this.lastTouchX = e.touches[0].pageX;
this.lastTouchY = e.touches[0].pageY;
this.velocityX = 0;
this.velocityY = 0;
this.accumulatedDeltaX = 0;
this.accumulatedDeltaY = 0;
this.lastFrameTimestamp = Date.now();
clearInterval(this.trackerId);
this.trackerId = setInterval(this.track, TRACKER_TIMEOUT);
if (this.stopPropagation()) {
e.stopPropagation();
}
};
TouchHandler.prototype.onTouchEnd = function onTouchEnd(e) {
this.onTouchCancel(e);
requestAnimationFrame(this.startAutoScroll);
};
TouchHandler.prototype.onTouchCancel = function onTouchCancel(e) {
clearInterval(this.trackerId);
this.trackerId = null;
if (this.stopPropagation()) {
e.stopPropagation();
}
};
TouchHandler.prototype.onTouchMove = function onTouchMove(e) {
var moveX = e.touches[0].pageX;
var moveY = e.touches[0].pageY;
// Compute delta scrolled since last drag
// Mobile, scrolling is inverted
this.deltaX = MOVE_AMPLITUDE * (this.lastTouchX - moveX);
this.deltaY = MOVE_AMPLITUDE * (this.lastTouchY - moveY);
var handleScrollX = this.handleScrollX(this.deltaX, this.deltaY);
var handleScrollY = this.handleScrollY(this.deltaY, this.deltaX);
if (!handleScrollX && !handleScrollY) {
return;
}
// If we can handle scroll update last touch for computing delta
if (handleScrollX) {
this.lastTouchX = moveX;
} else {
this.deltaX = 0;
}
if (handleScrollY) {
this.lastTouchY = moveY;
} else {
this.deltaY = 0;
}
e.preventDefault();
// ensure minimum delta magnitude is met to avoid jitter
var changed = false;
if (Math.abs(this.deltaX) > 2 || Math.abs(this.deltaY) > 2) {
if (this.stopPropagation()) {
e.stopPropagation();
}
changed = true;
}
// Request animation frame to trigger scroll of computed delta
if (changed === true && this.dragAnimationId === null) {
this.dragAnimationId = requestAnimationFrame(this.didTouchMove);
}
};
return TouchHandler;
}();
export default TouchHandler;