Springs and states
Every continuous quantity in the character is a damped spring. Nothing is tweened, and there are no keyframes.
The integrator
Semi-implicit Euler at a fixed substep of 1/120 second. Semi-implicit is stable at these frequencies and costs a third of what RK4 does, which matters when several hundred springs tick per frame.
Why a fixed substep
Integrating with the frame delta directly makes the simulation frame-rate dependent: the same spring settles faster on a 144 Hz display than on a 60 Hz one, and stutters visibly when the frame rate is uneven.
Accumulating fixed substeps instead means a 144 Hz monitor and a 60 Hz monitor converge to the same value at the same wall-clock time.
| rate | value after 1s |
|---|---|
| 60 Hz | 0.980837 |
| 144 Hz | 0.981133 |
Damping
ζ = 1 is critically damped: the fastest approach with no overshoot. Below one the spring overshoots and rings, which is what gives squash its rubberiness and the notification badge its pop.
Impulses
Some motion is better expressed as a kick than as a curve. Instead of moving a target, add directly to velocity:
The spring absorbs it and settles on its own. angry does this every 1.8 to 3.2 seconds, which produces a recoil that would be tedious to author as an explicit animation and which never looks mechanically identical twice.
Behaviours
A behaviour returns four targets per frame. idle is three sine waves at unrelated frequencies:
Because 0.5, 0.17, 0.27 and 0.85 share no common period, the combined motion does not repeat on any human timescale. That is the difference between a character that feels alive in a sidebar and one that is visibly a loop.
Exporting a spring to CSS
Some motion has to run as a CSS transition rather than on the JS ticker. springToLinearEasing samples a spring into a linear() easing function so the curve matches:
linear(0, 0.1154, 0.3501, 0.592, 0.7878, 0.9221, 1.0004, 1.0367, 1.046, 1.0409, 1.0302, 1.019, 1.0099)Reduced motion
When the reader prefers reduced motion, springs are settled to their targets instead of stepped, one frame is drawn, and no loop starts. The character is correct and static rather than absent.
Next
- Belts and particles — how belt states are drawn
- States — every animation state, grouped