pithos
Version:
Advanced JavaScript/TypeScript superset providing performance, gestures, animations, and DOM utilities
161 lines • 5.78 kB
JavaScript
import { distance } from "../math/geometry";
export class WheelGestureHandler {
constructor(element = document.body, options = {}, callbacks = {}) {
this.isWheeling = false;
this.startElement = null;
this.lastEventTime = 0;
this.lastPosition = null;
this.timeout = null;
this.recentDeltas = [];
this.maxHistorySize = 5;
this.handleWheel = (event) => {
if (!event.ctrlKey) {
if (this.isWheeling) {
this.stopGesture();
}
return;
}
const now = performance.now();
const currentPosition = { x: event.clientX, y: event.clientY };
const deltaAbs = Math.abs(event.deltaY || event.deltaX);
if (this.detectNewGesture(event)) {
if (this.isWheeling) {
if (this.timeout) {
clearTimeout(this.timeout);
}
this.onEnd?.({
startElement: this.startElement,
lastPosition: this.lastPosition,
});
}
this.isWheeling = true;
this.startElement = event.target;
this.lastPosition = currentPosition;
this.recentDeltas = [];
this.onStart?.({
event,
startElement: this.startElement,
position: currentPosition,
});
}
this.recentDeltas.push(deltaAbs);
if (this.recentDeltas.length > this.maxHistorySize) {
this.recentDeltas.shift();
}
const adaptiveDelay = this.calculateAdaptiveDelay();
const phase = this.getMovementPhase();
if (this.startElement && event.target) {
this.onWheel?.({
event,
startElement: this.startElement,
currentElement: event.target,
distance: this.lastPosition
? distance(this.lastPosition, currentPosition)
: 0,
adaptiveDelay,
phase,
});
}
this.lastEventTime = now;
this.lastPosition = currentPosition;
if (this.timeout) {
clearTimeout(this.timeout);
}
this.timeout = window.setTimeout(() => {
this.stopGesture();
}, adaptiveDelay);
};
this.handleKeyUp = (event) => {
if (event.key === "Control" && this.isWheeling) {
this.stopGesture();
}
};
this.element = element;
this.maxLogicalDistance = options.maxLogicalDistance || 100;
this.minDelay = options.minDelay || 80;
this.maxDelay = options.maxDelay || 300;
this.onStart = callbacks.onStart;
this.onWheel = callbacks.onWheel;
this.onEnd = callbacks.onEnd;
this.init();
}
calculateAdaptiveDelay() {
if (this.recentDeltas.length === 0)
return this.maxDelay;
const avgDelta = this.recentDeltas.reduce((a, b) => a + b, 0) / this.recentDeltas.length;
let delay;
if (avgDelta < 1) {
delay = this.maxDelay;
}
else if (avgDelta > 10) {
delay = this.minDelay;
}
else {
const ratio = (10 - avgDelta) / 9;
delay = this.minDelay + (this.maxDelay - this.minDelay) * ratio;
}
return Math.round(delay);
}
getMovementPhase() {
if (this.recentDeltas.length === 0)
return "normal";
const avgDelta = this.recentDeltas.reduce((a, b) => a + b, 0) / this.recentDeltas.length;
if (avgDelta < 2)
return "fine-tuning";
if (avgDelta > 8)
return "fast";
return "normal";
}
detectNewGesture(event) {
const now = performance.now();
const currentPosition = { x: event.clientX, y: event.clientY };
if (!this.isWheeling) {
return true;
}
const adaptiveThreshold = this.calculateAdaptiveDelay() * 0.4;
const timeSinceLastEvent = now - this.lastEventTime;
if (timeSinceLastEvent > adaptiveThreshold) {
return true;
}
if (this.lastPosition) {
const d = distance(this.lastPosition, currentPosition);
const maxAcceptableDistance = this.maxLogicalDistance * Math.min(timeSinceLastEvent / 20, 2);
if (d > maxAcceptableDistance) {
return true;
}
}
return false;
}
stopGesture() {
if (this.isWheeling) {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
this.onEnd?.({
startElement: this.startElement,
lastPosition: this.lastPosition,
});
}
this.isWheeling = false;
this.startElement = null;
this.lastPosition = null;
this.recentDeltas = [];
this.lastEventTime = 0;
}
init() {
this.element.addEventListener("wheel", this.handleWheel, {
passive: false,
});
this.element.addEventListener("keyup", this.handleKeyUp);
}
destroy() {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
this.element.removeEventListener("wheel", this.handleWheel);
this.element.removeEventListener("keyup", this.handleKeyUp);
}
}
//# sourceMappingURL=wheel-handler.js.map