| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155 |
- import { frame, isPrimaryPointer, cancelFrame, frameData } from 'motion-dom';
- import { secondsToMilliseconds, millisecondsToSeconds } from 'motion-utils';
- import { addPointerEvent } from '../../events/add-pointer-event.mjs';
- import { extractEventInfo } from '../../events/event-info.mjs';
- import { distance2D } from '../../utils/distance.mjs';
- import { pipe } from '../../utils/pipe.mjs';
- /**
- * @internal
- */
- class PanSession {
- constructor(event, handlers, { transformPagePoint, contextWindow, dragSnapToOrigin = false, } = {}) {
- /**
- * @internal
- */
- this.startEvent = null;
- /**
- * @internal
- */
- this.lastMoveEvent = null;
- /**
- * @internal
- */
- this.lastMoveEventInfo = null;
- /**
- * @internal
- */
- this.handlers = {};
- /**
- * @internal
- */
- this.contextWindow = window;
- this.updatePoint = () => {
- if (!(this.lastMoveEvent && this.lastMoveEventInfo))
- return;
- const info = getPanInfo(this.lastMoveEventInfo, this.history);
- const isPanStarted = this.startEvent !== null;
- // Only start panning if the offset is larger than 3 pixels. If we make it
- // any larger than this we'll want to reset the pointer history
- // on the first update to avoid visual snapping to the cursoe.
- const isDistancePastThreshold = distance2D(info.offset, { x: 0, y: 0 }) >= 3;
- if (!isPanStarted && !isDistancePastThreshold)
- return;
- const { point } = info;
- const { timestamp } = frameData;
- this.history.push({ ...point, timestamp });
- const { onStart, onMove } = this.handlers;
- if (!isPanStarted) {
- onStart && onStart(this.lastMoveEvent, info);
- this.startEvent = this.lastMoveEvent;
- }
- onMove && onMove(this.lastMoveEvent, info);
- };
- this.handlePointerMove = (event, info) => {
- this.lastMoveEvent = event;
- this.lastMoveEventInfo = transformPoint(info, this.transformPagePoint);
- // Throttle mouse move event to once per frame
- frame.update(this.updatePoint, true);
- };
- this.handlePointerUp = (event, info) => {
- this.end();
- const { onEnd, onSessionEnd, resumeAnimation } = this.handlers;
- if (this.dragSnapToOrigin)
- resumeAnimation && resumeAnimation();
- if (!(this.lastMoveEvent && this.lastMoveEventInfo))
- return;
- const panInfo = getPanInfo(event.type === "pointercancel"
- ? this.lastMoveEventInfo
- : transformPoint(info, this.transformPagePoint), this.history);
- if (this.startEvent && onEnd) {
- onEnd(event, panInfo);
- }
- onSessionEnd && onSessionEnd(event, panInfo);
- };
- // If we have more than one touch, don't start detecting this gesture
- if (!isPrimaryPointer(event))
- return;
- this.dragSnapToOrigin = dragSnapToOrigin;
- this.handlers = handlers;
- this.transformPagePoint = transformPagePoint;
- this.contextWindow = contextWindow || window;
- const info = extractEventInfo(event);
- const initialInfo = transformPoint(info, this.transformPagePoint);
- const { point } = initialInfo;
- const { timestamp } = frameData;
- this.history = [{ ...point, timestamp }];
- const { onSessionStart } = handlers;
- onSessionStart &&
- onSessionStart(event, getPanInfo(initialInfo, this.history));
- this.removeListeners = pipe(addPointerEvent(this.contextWindow, "pointermove", this.handlePointerMove), addPointerEvent(this.contextWindow, "pointerup", this.handlePointerUp), addPointerEvent(this.contextWindow, "pointercancel", this.handlePointerUp));
- }
- updateHandlers(handlers) {
- this.handlers = handlers;
- }
- end() {
- this.removeListeners && this.removeListeners();
- cancelFrame(this.updatePoint);
- }
- }
- function transformPoint(info, transformPagePoint) {
- return transformPagePoint ? { point: transformPagePoint(info.point) } : info;
- }
- function subtractPoint(a, b) {
- return { x: a.x - b.x, y: a.y - b.y };
- }
- function getPanInfo({ point }, history) {
- return {
- point,
- delta: subtractPoint(point, lastDevicePoint(history)),
- offset: subtractPoint(point, startDevicePoint(history)),
- velocity: getVelocity(history, 0.1),
- };
- }
- function startDevicePoint(history) {
- return history[0];
- }
- function lastDevicePoint(history) {
- return history[history.length - 1];
- }
- function getVelocity(history, timeDelta) {
- if (history.length < 2) {
- return { x: 0, y: 0 };
- }
- let i = history.length - 1;
- let timestampedPoint = null;
- const lastPoint = lastDevicePoint(history);
- while (i >= 0) {
- timestampedPoint = history[i];
- if (lastPoint.timestamp - timestampedPoint.timestamp >
- secondsToMilliseconds(timeDelta)) {
- break;
- }
- i--;
- }
- if (!timestampedPoint) {
- return { x: 0, y: 0 };
- }
- const time = millisecondsToSeconds(lastPoint.timestamp - timestampedPoint.timestamp);
- if (time === 0) {
- return { x: 0, y: 0 };
- }
- const currentVelocity = {
- x: (lastPoint.x - timestampedPoint.x) / time,
- y: (lastPoint.y - timestampedPoint.y) / time,
- };
- if (currentVelocity.x === Infinity) {
- currentVelocity.x = 0;
- }
- if (currentVelocity.y === Infinity) {
- currentVelocity.y = 0;
- }
- return currentVelocity;
- }
- export { PanSession };
|