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.
-
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.
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.
linear() and not a keyframe dumpA 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.
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 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.
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.