@antv/x6-react-components
Version:
React components for building x6 editors
175 lines • 7.06 kB
JavaScript
import { getJudgeFunction } from '../fn';
import { requestAnimationFrame } from './animationFrame';
export class TouchHandler {
constructor(options) {
this.didTouchMove = () => {
// Fire scroll callback based on computed drag delta.
// Also track accummulated delta so we can calculate velocity
this.dragAnimationId = null;
this.callback(this.deltaX, this.deltaY);
this.accumulatedDeltaX += this.deltaX;
this.accumulatedDeltaY += this.deltaY;
this.deltaX = 0;
this.deltaY = 0;
};
this.track = () => {
// 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.
const now = Date.now();
const elapsed = now - this.lastFrameTimestamp;
const oldVelocityX = this.velocityX;
const 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
let weight = 0.8;
if (elapsed < TouchHandler.TRACKER_TIMEOUT) {
weight *= elapsed / TouchHandler.TRACKER_TIMEOUT;
}
if (oldVelocityX === 0 && oldVelocityY === 0) {
weight = 1;
}
// Formula for computing weighted average of velocity
this.velocityX =
weight *
((TouchHandler.TRACKER_TIMEOUT * this.accumulatedDeltaX) / (1 + elapsed));
if (weight < 1) {
this.velocityX += (1 - weight) * oldVelocityX;
}
this.velocityY =
weight *
((TouchHandler.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 = () => {
// 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 = () => {
// 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)
const elapsed = Date.now() - this.autoScrollTimestamp;
const factor = TouchHandler.DECELERATION_AMPLITUDE *
Math.exp(-elapsed / TouchHandler.DECELERATION_FACTOR);
let deltaX = factor * this.velocityX;
let 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.callback(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.callback = options.onTouchScroll;
this.handleScrollX = getJudgeFunction(options.shouldHandleScrollX);
this.handleScrollY = getJudgeFunction(options.shouldHandleScrollY);
this.stopPropagation = getJudgeFunction(options.stopPropagation);
}
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();
if (this.trackerId != null) {
clearInterval(this.trackerId);
}
this.trackerId = window.setInterval(this.track, TouchHandler.TRACKER_TIMEOUT);
if (this.stopPropagation()) {
e.stopPropagation();
}
}
onTouchEnd(e) {
this.onTouchCancel(e);
requestAnimationFrame(this.startAutoScroll);
}
onTouchCancel(e) {
if (this.trackerId != null) {
clearInterval(this.trackerId);
this.trackerId = null;
}
if (this.stopPropagation()) {
e.stopPropagation();
}
}
onTouchMove(e) {
const moveX = e.touches[0].pageX;
const moveY = e.touches[0].pageY;
// Compute delta scrolled since last drag
// Mobile, scrolling is inverted
this.deltaX = TouchHandler.MOVE_AMPLITUDE * (this.lastTouchX - moveX);
this.deltaY = TouchHandler.MOVE_AMPLITUDE * (this.lastTouchY - moveY);
const handleScrollX = this.handleScrollX(this.deltaX, this.deltaY);
const 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
let 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 && this.dragAnimationId == null) {
this.dragAnimationId = requestAnimationFrame(this.didTouchMove);
}
}
}
(function (TouchHandler) {
TouchHandler.MOVE_AMPLITUDE = 1.6;
TouchHandler.DECELERATION_AMPLITUDE = 1.6;
TouchHandler.DECELERATION_FACTOR = 325;
TouchHandler.TRACKER_TIMEOUT = 100;
})(TouchHandler || (TouchHandler = {}));
//# sourceMappingURL=TouchHandler.js.map