Damp
阻尼跟随 · dampKeep pulling the current value a fraction of the way toward its target — measured in time, not in frames.
x += (target − x) * k is the most common smoothing on the site (53 studies), but it silently assumes exactly 60 frames per second: the same k follows much faster on a 120 Hz screen and slower when frames drop. Replace the per-frame factor with 1 − e^(−λ·dt) and the same elapsed time gives the same position at any refresh rate.
Formula
x ← x + (target − x) · (1 − e^(−λ·dt)) λ = −60 · ln(1 − k) port a 60 Hz factor k exactly half-life = ln 2 / λ time to close half the gap
Parameters
| This page | Mature libraries | |
|---|---|---|
| k (60 Hz) | 0.1 | Lenis lerp: 0.1The old per-frame factor, used only to derive λ. |
| λ | −60·ln(0.9) ≈ 6.32 /s | Lenis uses lerp × 60 = 6 /s60k is an approximation — about 5% slow at k = 0.1, worse as k grows. |
| half-life | ≈ 110 ms | —Half-life is the more intuitive knob: how long until half the gap is gone. |
| dt clamp | ≤ 40 ms | Motion maxElapsed = 40 |
Doing it well
- Multiply by time, not frames:
lerp(x, y, 1 − e^(−λ·dt)). This is Lenis'sdamp(), and the conclusion of Rory Driscoll's 2016 post on frame-rate-independent damping.lenis packages/core/src/maths.ts · animate.ts:43 - Port old constants exactly:
λ = −60·ln(1 − k). Using60knoticeably slows down large factors such as 0.2 or 0.3. - Clamp dt. The first frame after a tab comes back can be 10 s; unclamped, damp jumps straight to the end. Clamping to 40 ms keeps the transition visible.motion-dom/src/frameloop/batcher.ts:6
- Damp is first-order: when the target jumps, velocity jumps with it. If the start must be soft too, or the follow should carry momentum, use a spring.
In our cases
49 studies integrate per frame somewhere (x += (t − x) * k, v *= k). Of the 325 dynamics uses Codex read, 119 advance per frame and only 18 multiply by dt. lifecycle.animate in rewrite/lib/case-runtime.js already passes a clamped step every frame — the studies just ignore it.
49 / 69 cases · integrate per frame (no dt)From Codex per-case quality flags (keyword-clustered, approximate)
Used in 53 cases
+35 more
Reference implementation · basics.js
/**
* Exponential approach: frame-rate independent replacement for `x += (target - x) * k`.
* lambda is "how many e-foldings per second". To port a 60 Hz factor k: lambda = -60 * ln(1 - k).
*/
export const damp = (current, target, lambda, dt) => lerp(current, target, 1 - Math.exp(-lambda * dt));
export const lambdaFrom60 = k => -60 * Math.log(1 - k);
export const lambdaFromHalfLife = seconds => Math.LN2 / seconds;
/** The per-frame version found in 49 of our 69 cases — only correct at exactly 60 Hz. */
export const dampPerFrame = (current, target, k) => current + (target - current) * k;