UNPKG

@antv/x6-react-components

Version:

React components for building x6 editors

175 lines 7.06 kB
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