Dynamics · 18
Inertia
惯性衰减 · inertiaDecay release velocity over time and predict the landing point at release.
A dragged object should not halt at release. Exponential velocity decay has an exact integral, so the landing point is the current position plus velocity times the time constant. Multiplying velocity by one friction number per frame changes the travel distance with refresh rate.
Formula
v(t) = v₀·e^(−t/τ) x(t) = x₀ + v₀·τ·(1 − e^(−t/τ)) landing = x₀ + v₀·τ
Parameters
| This page | Mature libraries | |
|---|---|---|
| timeConstant | 0.35 s | Motion 325 ms |
| power | velocity × τ | Motion power 0.8 |
Doing it well
- Compute the eventual landing point at release so snapping can choose a target.motion packages/motion-dom/src/animation/generators/inertia.ts:35–39
- Advance both velocity and displacement with the time based exponential solution; fixed per-frame friction changes decay and distance on high refresh displays.
- Motion’s inertia generator defaults to power 0.8 and a 325 ms time constant, with
modifyTargetavailable to adjust the target.motion packages/motion-dom/src/animation/generators/inertia.ts:7–16,35–39
In our cases
19 studies use inertia. momentum-slider, drag-gallery and formation turn a drag into continued motion; phase 2 also flags per-frame integration in this family as a refresh-rate risk.
49 / 69 cases · integrate per frame (no dt)From Codex per-case quality flags (keyword-clustered, approximate)
Used in 19 cases
Reference implementation · basics.js
/**
* Exponential decay after a throw. v(t) = v0·e^(−t/τ), so it lands at x0 + v0·τ.
* τ ≈ 0.5 s matches iOS UIScrollView "normal" (decelerationRate 0.998 per ms); 0.1 s ≈ "fast".
*/
export class Inertia {
constructor({ value = 0, velocity = 0, timeConstant = 0.35, min = -Infinity, max = Infinity } = {}) {
Object.assign(this, { value, velocity, timeConstant, min, max });
}
/** Where it will come to rest if nothing intervenes. */
get projected() { return this.value + this.velocity * this.timeConstant; }
step(dt) {
const decay = Math.exp(-dt / this.timeConstant);
this.value += this.velocity * this.timeConstant * (1 - decay); // exact integral over dt
this.velocity *= decay;
if (this.value < this.min || this.value > this.max) {
this.value = clamp(this.value, this.min, this.max); this.velocity = 0;
}
if (Math.abs(this.velocity) < 0.5) this.velocity = 0;
return this.value;
}
}
/** The per-frame version found in our cases: `x += v; v *= friction` — speed depends on refresh rate. */
export const inertiaPerFrame = (state, friction) => { state.value += state.velocity; state.velocity *= friction; };