Input · 12
Release velocity
释放速度 · velocity-estimatorEstimate throw speed from recent positions and expire it after the pointer stops.
The last pair of events is noisy and depends on event timing. A user can drag quickly, pause, then release while an old speed still causes a throw. Average over a short window and set release speed to zero once the last sample is stale.
Formula
v = (last.position − first.position) / Δt first = oldest sample within window if releaseTime − last.time > stale: v = 0
Parameters
| This page | Mature libraries | |
|---|---|---|
| window | 80 ms | use-gesture uses event timestampsUse recent points, not just the final segment. |
| stale | 32 ms | use-gesture 32 ms |
Doing it well
- Timestamp each sample and divide by elapsed time for px/s; events are not frames.@use-gesture core/src/engines/Engine.ts:340–353
- Zero the release velocity when no movement arrived for over 32 ms; use-gesture explicitly handles that final gap.@use-gesture core/src/engines/Engine.ts:8–21
- Freeze the estimate at release and pass it to inertia or a spring; do not keep rewriting it in animation frames.
In our cases
24 studies estimate velocity. The quality notes for momentum-slider and drag-gallery both flag stale speed reused at release: a fast drag can still fling after a pause.
19 / 69 cases · release velocity without a stale checkFrom Codex per-case quality flags (keyword-clustered, approximate)
Used in 24 cases
Reference implementation · basics.js
/**
* Release velocity from recent pointer samples.
* window: average over the last ~80 ms (one event is too noisy)
* stale: if the pointer has been still for > 32 ms, the user stopped — velocity is 0 (use-gesture)
*/
export class VelocityTracker {
constructor({ window = 0.08, stale = 0.032 } = {}) { this.window = window; this.stale = stale; this.samples = []; }
reset() { this.samples.length = 0; }
add(time, x, y = 0) {
this.samples.push({ time, x, y });
while (this.samples.length > 2 && time - this.samples[0].time > this.window * 2) this.samples.shift();
}
velocity(now) {
const s = this.samples, last = s[s.length - 1];
if (!last || now - last.time > this.stale) return { x: 0, y: 0 };
let first = last;
for (let i = s.length - 2; i >= 0 && last.time - s[i].time <= this.window; i--) first = s[i];
const dt = last.time - first.time;
return dt > 0 ? { x: (last.x - first.x) / dt, y: (last.y - first.y) / dt } : { x: 0, y: 0 };
}
}