Command Palette

Search for a command to run...

Hectal
Shots in this scene
SHOT 5.2Intermediate ~20 min · shot 2 of 6

Inside cubic-bezier()

In one line

cubic-bezier(x1, y1, x2, y2) draws an easing curve with two handles. The two x values must stay between 0 and 1; the two y values may go outside, and that is how a move overshoots its target.

0/6 · 0%

Think of it like this

Bend a thin strip of wood between two pins, then pull its middle with two strings. Each string drags part of the strip towards it, and the curve you get is smooth. A Bézier curve is that strip: two fixed ends and two handles that pull.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Bézier curve
A smooth curve defined by its end points and by handle points that pull the curve towards them. Used for easing, fonts and vector drawing.
handle
One of the two control points in cubic-bezier(). The curve leans towards it but does not pass through it.
overshoot
Going past the final value and coming back. In an easing it comes from a y above 1.
anticipation
A small move in the wrong direction before the real one, like a runner leaning back. In an easing it comes from a y below 0.
invalid value
A value the browser cannot parse. It ignores the declaration and uses the next one in the cascade, with no error.

Step by step

01Drag the handles

In the easing lab, drag the first handle straight up. The curve becomes steeper at the start and the dot leaves quickly. Drag the second handle down and left and the end gets steeper instead. Read the CSS the lab prints under the curve: those four numbers are all there is.

02Push y past 1

Drag the second handle above the top of the box (y2 above 1). The curve rises past the top line and comes back. In the dot track the dot goes past the end and returns. Now drag a handle sideways past the left or right edge: the lab stops it, because x must stay between 0 and 1.

03Measure the overshoot

Run "Overshoot past the edge" and press Go. Pause, step to about 360 ms. The top (out) box is at 157.4 px and still creeping. The bottom (back-out) box is at 175.5 px, 15.5 px past its 160 px target, so its right edge sticks out of the dashed track. It is back at exactly 160 px at 600 ms.

04Read what the browser accepted

Run "Bad curves fail quietly". The console shows each row's computed timing function: only the valid row keeps its curve. The other three show ease, and the last has duration 0s. The CSS.supports lines say the same: x1 = 1.5 is refused, y = 1.5 is accepted.

Try it yourself

  1. 1

    Put the overshoot to work, then limit it

    In "Overshoot past the edge" change the blue curve's 1.56 to 1.1. The box now peaks at only 160.4 px, 0.4 px past its target: no visible overshoot. Change it to 2.5: it peaks at 231.4 px, 71 px past the target and far outside the track. The y value is the overshoot dial.

  2. 2

    Spot the dropped shorthand

    In "Bad curves fail quietly" press Go. The first three dots move (two of them with plain ease). The fourth does not move at all: its transition shorthand held the bad curve, so the whole shorthand was dropped and the dot jumps with no track.

Code & diagrams

Try it: interactive

Drag a curve

Switch to the Bézier mode, drag both handles and watch the marker on the curve. Push a handle above the box for overshoot, and try to drag one past the sides.

Overshoot past the edge

Both boxes move 160 px inside a 200 px track and should end exactly at its edge. The orange one uses Lumen's ease-out; the blue one a curve with a y above 1. Press Go, pause and step around 360 ms.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

a cubic-bezier(0.22, 1, 0.36, 1)
b cubic-bezier(0.34, 1.56, 0.64, 1)
Bad curves fail quietly

Four rows with four different curves. No errors appear anywhere; the console prints what each row actually got, and what CSS.supports() says about three curves.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

ok     cubic-bezier(0.22, 1, 0.36, 1)     0.5s
x1     ease                               0.5s
three  ease                               0.5s
short  ease                               0s
supports valid: true
supports x1 1.5: false
supports y 1.5: true

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Clip the overshoot

In "Overshoot past the edge" add overflow: hidden; to .track and press Go.

terminal
$ Press Go, pause and step to 360 ms
── what you'll see ──
The blue box is cut off at the track's right edge for part of the move: the 15 px or so that stick out disappear, then the box settles back inside.
Measured: the transform reaches 175.5 px against a 160 px target; the box is 40 px wide in a 200 px track.

Break #2

x outside 0 to 1

In "Overshoot past the edge" change the blue curve to cubic-bezier(1.5, 1.56, 0.64, 1). Press Go.

terminal
$ Read the console line for the blue box, then press Go
── what you'll see ──
b ease
Measured with the same x1: the value is refused and the computed easing is the default `ease`. No error.

Myth vs fact

Myth

A bigger y1 or y2 always means a faster animation.

Fact

The duration never changes. A y above 1 adds an overshoot; a high y1 only makes the start steeper. The move still ends at the same instant.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    A curve with y outside 0 to 1 applies to any animated property. For opacity the value is clamped to 0–1 when drawn, so an overshooting fade simply reaches full opacity early and stays there. For transform and width, nothing clamps.

Remember this

  1. 1

    The problem this solves: the five keywords are not enough. A designer asks for "fast start, long soft landing" or "a little overshoot, then settle". You need a way to describe any smooth speed shape in four numbers that fit in one line of CSS.

  2. 2

    Two handles. The curve starts at (0, 0) (time 0, progress 0) and ends at (1, 1). cubic-bezier(x1, y1, x2, y2) places two handles: the first at (x1, y1) pulls the start of the curve, the second at (x2, y2) pulls the end. The curve never touches the handles; it leans towards them. The keywords are only names for four such handles: ease-out is cubic-bezier(0, 0, 0.58, 1).

  3. 3

    Pull the first handle up, and the start gets steeper. A high y1 with a small x1 makes the curve climb at once: a fast start. A low y2 or a large x2 flattens the end. Lumen's --ease-out is cubic-bezier(0.22, 1, 0.36, 1): the first handle is at the very top, so 40% of the move is done after only 10% of the time, and 76% after a quarter of it. It is a strong ease-out.

  4. 4

    The maths, last. The curve is drawn by a parameter t from 0 to 1: x(t) = 3(1−t)²t·x1 + 3(1−t)t²·x2 + t³, and the same for y with y1 and y2. But the browser has the time fraction, not t. So it first solves x(t) = time for t (numerically; this is why x must be well behaved), then returns y(t) as the progress.

  5. 5

    x stays in 0 to 1, y can go anywhere. If x1 or x2 were outside 0 to 1, the curve could bend backwards in time and give two different answers for one moment. So the CSS rule is strict: x outside 0 to 1 makes the value invalid and the browser ignores it. y has no such limit. A y above 1 means the value goes past its target; a y below 0 means it first moves the wrong way (anticipation).

  6. 6

    Overshoot is real movement. cubic-bezier(0.34, 1.56, 0.64, 1) on a 160 px move peaks at 175.6 px, about 344 ms into 600 ms: 15.6 px beyond the target, then returns. An element that ends at its container's edge will visibly leave the container, and may be cut off by overflow: hidden. The first example shows both curves in a 200 px track.

  7. 7

    An invalid curve fails silently. cubic-bezier(1.5, 0, 0.5, 1), or one with three numbers, is not an error you can see: the browser drops the declaration and uses the next-best value, here the default ease. Inside the transition shorthand the whole shorthand is dropped, so there is no transition at all. CSS.supports() tells you what the browser accepts.

Explain it without notes

01

Why must x1 and x2 stay between 0 and 1 in cubic-bezier()?

02

What does a y2 above 1 do, and what must you watch for?

03

What happens if you write transition: transform 300ms cubic-bezier(1.5, 0, 0.5, 1)?

Practice

01

Write a curve that leaves quickly and lands softly but never overshoots, and test it with CSS.supports.

02

Write a curve with a small anticipation (the element first moves a few percent the wrong way), and say which handle makes it.

Done when you can

  • You can say what the four numbers of cubic-bezier() mean

  • You can explain why x is limited to 0 to 1 and y is not

  • You can make an overshoot and tell when it will be clipped

  • You know an invalid curve is dropped silently, and the shorthand with it