Spring Bench.

Tune a spring, get the CSS. The plot is where the animated thing actually is at every moment; the lanes underneath are the browser's own animation engine running the easing this page just wrote. Springs are not a CSS timing function, but a spring chopped into enough straight segments is one, and the readout under the plot says what that costs.

Step response

the spring the linear() the browser will run settle band and settle time target
Regime-
Damping ζ-
Overshoot-
Settles in-
Stops-
Shape error-

 

The browser running it

Move-
translate
Race against ease-out, same durationspring on top
spring
ease-out
Pop inscale and fade
one
two
three
space replays · keys 1 to 6 load a preset · every change replays itself

Preset

Spring

Sampling

CSS

-
What this is actually doing

The spring

One equation: m x'' + c x' + k x = k, a mass on a spring with a damper, pulled toward a target at 1. Only two numbers matter for the shape: w0 = sqrt(k/m), how fast it wants to oscillate, and zeta = c / (2*sqrt(k*m)), how much of that oscillation survives. Below zeta = 1 it overshoots and comes back; at exactly 1 it arrives as fast as it can without ever passing the target; above 1 it is sluggish. Mass, stiffness and damping are three knobs on two numbers, which is why the "by feel" pane, which drives period and bounciness directly, is the one that actually behaves.

Why the solution is written the way it is

The usual textbook split gives three separate formulas for the three regimes, and the overdamped one divides by r1 - r2 = 2*w0*sqrt(zeta^2 - 1). That denominator is zero at critical damping, so at zeta exactly 1 it returns NaN, and just above it you scale an enormous coefficient by the difference between two nearly identical exponentials. Measured against a numerical integration, that costs up to about 1e-8 of travel, which is much less than I predicted and is written here as the measurement rather than as the guess. This page instead uses one expression for all three: e^(-zeta*w0*t) * (C(t) + (zeta*w0 - v0) * S(t)), where C is cosh or cos and S is sinh(x)/x or sin(x)/x depending on the sign of w0^2*(zeta^2 - 1). Nothing small is ever a denominator, and critical damping stops being a special case at all: it is the limit both branches already agree on.

Why linear() and not a keyframe dump

A CSS timing function has to be a function of progress, and a spring is not one of the four the language ships. linear() takes a list of output values and joins them with straight lines, which means a spring becomes a timing function as soon as you are willing to say how many segments it gets. That is a real, quantified trade: piecewise linear error goes as (h^2/8)*max|f''|, so every doubling of the stop count divides the worst error by four. The panel shows you the number rather than asking you to trust it.

Adaptive stops

A spring's curvature is nearly all in the first bounce, and its tail is a straight line pretending to be a curve. Spacing stops evenly spends most of them on the boring part. Ticking "space the stops adaptively" runs a Douglas-Peucker pass over a dense sampling of the curve, keeps the points that carry shape, and writes their positions explicitly as percentages. Same accuracy, roughly half the numbers.

The duration is part of the answer

The easing is in normalised time, so it only means what it says when the animation lasts exactly as long as the window the curve was sampled over. Change the duration and keep the easing and you have a stretched impostor of the motion you tuned. Copy both lines or neither.

Settling is a choice

A spring never arrives. It gets closer. "How long does this take" is really "how close counts as arrived", which is why that threshold is a slider and not a constant, and why tightening it does not improve the animation, it only makes it longer.