Easing Curve Editor
Drag a cubic bezier, count out steps, or simulate a spring and take it as CSS
The curve
What it looks like
Ready made
The CSS
Drag the handles and watch three things move with the result. The preview runs the real CSS through the browser instead of redrawing the curve in JavaScript, so what you see is what the animation will do.
What the curve means
x is time, y is progress.
The curve always starts at 0,0 and ends at 1,1.
You move the two handles in between.
The handles can go above the top of the box.
That is not a mistake: a curve that passes 1 and
comes back is an overshoot, and it is how a thing
lands with a bit of weight to it.Time runs left to right and progress runs bottom to top. A straight diagonal is constant speed. A curve that starts flat and steepens is something building up, and one that starts steep and flattens is something arriving.
The dashed diagonal is there for comparison. Above it means ahead of where a constant speed would be, below it means behind. Most good easing spends its time above the line, because things that get going quickly and settle slowly feel responsive.
Why the browser has to solve for it
A bezier gives you x and y as functions of some
parameter t.
CSS needs y as a function of x, because x is
time and time is what it has.
There is no formula for that. Browsers solve it
per frame with Newton's method, falling back to
halving the interval when the curve is too flat
for Newton to find its footing. So does this.This is the reason a cubic-bezier is written with two points instead of as a function. The curve you draw is parametric, and the browser needs progress given a time. Turning one into the other has no closed form, so it is solved numerically, every frame, for every animation on the page.
It also explains the one hard rule: the x coordinates of both handles have to stay between 0 and 1. Let one outside and the curve can double back, which would mean the animation running backwards through time. The editor clamps x and leaves y alone, which is exactly the constraint CSS itself applies.
The keywords are just curves
ease cubic-bezier(0.25, 0.1, 0.25, 1)
ease-in cubic-bezier(0.42, 0, 1, 1)
ease-out cubic-bezier(0, 0, 0.58, 1)
ease-in-out cubic-bezier(0.42, 0, 0.58, 1)
Four keywords, four curves, nothing more. ease is
the default and is an odd shape nobody would draw
on purpose.There is nothing special about them. If you drag the handles onto one of those four positions the tool tells you so, because writing ease-out is clearer than writing the numbers.
ease being the default is worth knowing about. It is not symmetric, it is not what most designs want, and a great deal of the animation on the web is it by accident because nobody set anything else.
Springs
A spring is not a curve, it is an equation:
force = stiffness x (target - position)
- damping x velocity
CSS has no way to write that down. What it does
have is linear(), which takes a list of points.
So the spring is simulated here and the result
is written out as points.A spring has no duration of its own to be told. It has a mass, a stiffness and a damping, and how long it takes falls out of those. That is why springs feel different from curves. Interrupt one halfway and it carries its momentum with it. Interrupt a curve and it starts again from a fixed shape.
CSS cannot express one directly, so the simulation is run here and the result written out as a linear() list. That is what the animation libraries do too, and doing it up front means the animation runs on the compositor with no JavaScript on the page at all.
The list is thinned before it is written: the points where the curve actually turns are kept, and the ones sitting on a straight run between them are dropped. A thousand samples come down to a few dozen with no visible difference, and the duration shown underneath is the one to pair it with.
Steps
steps() jumps instead of gliding, which is how sprite sheet animation and typewriter effects work. The jump keyword decides which end gets skipped: jump-end holds the first frame and never shows the last one at all until the animation finishes, jump-start is the reverse, and jump-both shows neither end.
jump-none is the one that reaches both ends, by making the last step land exactly on 1. It is also the one that does nothing at all with a single step, which is legal and worth knowing before you spend ten minutes on it.
A note on how much
An overshoot of 1.1 reads as confident. An overshoot of 1.6 reads as a toy. The same goes for duration: most interface animation wants 150 to 300 milliseconds, and anything over half a second starts to feel like waiting.
The preview loops with a pause instead of animating back, because running the easing in reverse would show you a curve you did not choose. Everything here also respects a reduced motion preference and stops moving entirely if the machine asks for that.