js-draw
Version:
Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript.
104 lines (103 loc) • 4.07 kB
JavaScript
import { Vec2 } from '@js-draw/math';
export const defaultStationaryDetectionConfig = {
maxSpeed: 8.5, // screenPx/s
maxRadius: 11, // screenPx
minTimeSeconds: 0.5, // s
};
export default class StationaryPenDetector {
// Only handles one pen. As such, `startPointer` should be the same device/finger
// as `updatedPointer` in `onPointerMove`.
//
// A new `StationaryPenDetector` should be created for each gesture.
constructor(startPointer, config, onStationary) {
this.config = config;
this.onStationary = onStationary;
this.timeout = null;
this.stationaryStartPointer = startPointer;
this.lastPointer = startPointer;
this.averageVelocity = Vec2.zero;
this.setStationaryTimeout(this.config.minTimeSeconds * 1000);
}
// Returns true if stationary
onPointerMove(currentPointer) {
if (!this.stationaryStartPointer) {
// Destoroyed
return;
}
if (currentPointer.id !== this.stationaryStartPointer.id) {
return false;
}
// dx: "Δx" Displacement from last.
const dxFromLast = currentPointer.screenPos.minus(this.lastPointer.screenPos);
const dxFromStationaryStart = currentPointer.screenPos.minus(this.stationaryStartPointer.screenPos);
// dt: Delta time:
// /1000: Convert to s.
let dtFromLast = (currentPointer.timeStamp - this.lastPointer.timeStamp) / 1000; // s
// Don't divide by zero
if (dtFromLast === 0) {
dtFromLast = 1;
}
const currentVelocity = dxFromLast.times(1 / dtFromLast); // px/s
// Slight smoothing of the velocity to prevent input jitter from affecting the
// velocity too significantly.
this.averageVelocity = this.averageVelocity.lerp(currentVelocity, 0.5); // px/s
const dtFromStart = currentPointer.timeStamp - this.stationaryStartPointer.timeStamp; // ms
const movedOutOfRadius = dxFromStationaryStart.length() > this.config.maxRadius;
this.hasMovedOutOfRadius ||= movedOutOfRadius;
// If not stationary
if (movedOutOfRadius ||
this.averageVelocity.length() > this.config.maxSpeed ||
dtFromStart < this.config.minTimeSeconds) {
this.stationaryStartPointer = currentPointer;
this.lastPointer = currentPointer;
this.setStationaryTimeout(this.config.minTimeSeconds * 1000);
return false;
}
const stationaryTimeoutMs = this.config.minTimeSeconds * 1000 - dtFromStart;
this.lastPointer = currentPointer;
return stationaryTimeoutMs <= 0;
}
onPointerUp(pointer) {
if (pointer.id !== this.stationaryStartPointer?.id) {
this.cancelStationaryTimeout();
}
}
destroy() {
this.cancelStationaryTimeout();
this.stationaryStartPointer = null;
}
getHasMovedOutOfRadius() {
return this.hasMovedOutOfRadius;
}
cancelStationaryTimeout() {
if (this.timeout !== null) {
clearTimeout(this.timeout);
this.timeout = null;
}
}
setStationaryTimeout(timeoutMs) {
if (this.timeout !== null) {
return;
}
if (timeoutMs <= 0) {
this.onStationary(this.lastPointer);
}
else {
this.timeout = setTimeout(() => {
this.timeout = null;
if (!this.stationaryStartPointer) {
// Destroyed
return;
}
const timeSinceStationaryStart = performance.now() - this.stationaryStartPointer.timeStamp;
const timeRemaining = this.config.minTimeSeconds * 1000 - timeSinceStationaryStart;
if (timeRemaining <= 0) {
this.onStationary(this.lastPointer);
}
else {
this.setStationaryTimeout(timeRemaining);
}
}, timeoutMs);
}
}
}