Notes

Understanding transition timing functions

9 minute read

I’ve been trying to write animations over the past few days, and one thing has been pretty confusing: the four numbers inside cubic-bezier().

I know changing the numbers changed the feel of a transition. But I couldn’t really explain what a value such as cubic-bezier(0.25, 0.1, 0.25, 1) actually meant.

Turns out that they are the coordinates of two control handles on a graph.

What does a timing function do?

A transition’s duration answers how long the change or animation takes. Its timing function answers how the change is distributed across that time.

.button {
  transition: transform 800ms cubic-bezier(0.25, 0.1, 0.25, 1);
}

The browser repeatedly asks one question during those 800 milliseconds: “Given how much time has passed, how much of the property change should be complete?”

A timing function maps an input progress to an output progress. On the graph, input is time on the horizontal axis and output is completed progress on the vertical axis.

The control points of a CSS cubic Bézier timing function A time versus progress graph. The curve begins at zero, zero and ends at one, one. The first and second pairs of numbers position two handles that pull the curve.progresstime0101start (0, 0)end (1, 1)P₁ = (x₁, y₁)P₂ = (x₂, y₂)x = elapsed timey = completed progress
The endpoints are fixed. The four numbers position only the two control handles.

Both axes use normalized values. 0 means none and 1 means all. So x = 0.5 means half of the duration has elapsed, while y = 0.75 means the animated property has completed 75% of its journey. The x-values stay between 0 and 1 because they represent time, but y-values can go below 0 or above 1 to create effects like anticipation or overshoot. We’ll come back to that later.

Basically: at time x, use the curve to look up progress y.

The four values position two handles

The function is easier to read when you group the four values into two coordinate pairs. A cubic Bézier curve has four defining points, but CSS only lets you position the middle two:

  • P0 = (0, 0) is the fixed start.
  • P1 = (x1, y1) is the first movable handle.
  • P2 = (x2, y2) is the second movable handle.
  • P3 = (1, 1) is the fixed end.
cubic-bezier(x₁, y₁, x₂, y₂)

First handle

P1 = (x₁, y₁)

Sets the curve’s initial direction: how motion leaves the fixed starting point.

x₁
horizontal position - time axis
y₁
vertical position - progress axis

Second handle

P2 = (x₂, y₂)

Sets the curve’s final direction: how motion approaches the fixed ending point.

x₂
horizontal position - time axis
y₂
vertical position - progress axis

The four numbers are not points the curve must pass through. They position two handles that pull the curve into a shape, and that shape determines how the animation leaves the start, moves through the middle, and arrives at the end. These handles are often called control points. So P1 = (x1, y1) does not mean “at time x1, progress is y1.”

This is why x2 may be smaller than x1. The handles can cross horizontally because they are not ordered timestamps. CSS only requires each x-coordinate to stay between 0 and 1.

The familiar linear timing function is the simplest baseline: cubic-bezier(0, 0, 1, 1). In that representation, P1 overlaps P0, P2 overlaps P3, and all four points lie on the same diagonal. Progress therefore always equals elapsed time.

At each moment, the browser takes elapsed time as x, finds that x on the curve, and reads the curve’s y as progress. The progress comes from the curve not from either handle.

Read progress from the curve, not its control handles A time versus progress graph for cubic-bezier 0.5, 0.43, 0.25, 1. The second control handle sits at 25 percent time and 100 percent progress, but at 25 percent elapsed time the curve returns about 32 percent progress.elapsed timeprogress0101P₁P₂25%≈32%curve output

The slope is the speed

Now we can get from the shape of a curve to the feel of an animation. The slope tells us how quickly progress is changing as time passes:

  • A flat part of the curve makes little progress while time continues, so the motion is slow.
  • A steep part makes a lot of progress in a short time, so the motion is fast.
  • A straight diagonal has a constant slope, so it produces linear motion.
Flat at the startslow - fast
Constant slopesteady speed
Flat at the endfast - slow
Read each curve from left to right. Steeper sections spend progress faster.

The dashed line from the start to P1 is tangent to the beginning of the curve, so the first handle directly controls the starting velocity. The line from P2 to the end does the same for the ending velocity.

You can think of the starting slope as the handle’s vertical position divided by its horizontal position: how far it pulls upward or downward compared with how far it pulls forward in time. At the end, the same idea applies between the second handle and the fixed endpoint.

You do not need to calculate these slopes to choose an easing; they simply explain why moving a handle changes the way motion departs or arrives.

The familiar keywords are curves too

CSS’s named timing functions are aliases for particular cubic Bézier curves:

Keyword Equivalent curve Feel
linear cubic-bezier(0, 0, 1, 1) constant speed
ease-in cubic-bezier(.42, 0, 1, 1) slow departure
ease-out cubic-bezier(0, 0, .58, 1) soft arrival
ease-in-out cubic-bezier(.42, 0, .58, 1) soft at both ends

Why x is restricted but y is not

CSS requires x1 and x2 to be between 0 and 1. That keeps the curve valid as a timing function: each moment in time can map to one progress value. If either x value falls outside that range, the whole cubic-bezier() value is invalid.

The y values have no such restriction. A negative y value can make the property move backwards before it moves forward. A y value above 1 can carry the property beyond its destination and then bring it back. That is how a cubic Bézier curve can create anticipation or overshoot without a spring.

How the curve controls motion

Below is a visualizer of the same time-to-progress mapping. Drag either blue handles or adjust the four values, then press Play.

Watch the graph and the track together. Time advances evenly from left to right. At each moment, the dot’s vertical position shows the progress returned by the curve, and the ball underneath uses that same progress to move from start to finish.

The curve is not a path the ball follows through space it is the rule that controls how far the ball has travelled at each moment.

Interactive cubic Bézier graphA time versus progress graph with two draggable control points. Press Play to trace the curve and animate the ball below.elapsed timeprogress0101P₁P₂
cubic-bezier(0.25, 0.10, 0.25, 1.00)

Ready to play

Easing presets
first handle · time
first handle · progress
second handle · time
second handle · progress

CSS keeps x₁ and x₂ between 0 and 1; y values may extend beyond that range.

An easy mental model to keep

  1. x is elapsed time.
  2. y is completed progress.
  3. The start (0, 0) and end (1, 1) are fixed.
  4. The first pair positions the handle that shapes the departure.
  5. The second pair positions the handle that shapes the arrival.
  6. Flat means slow; steep means fast.
  7. The handles pull the curve. They are not points it must visit.

So when seeing cubic-bezier(x1, y1, x2, y2), picture two handles on a time–progress graph and ask: how should this motion leave, where should it accelerate, and how should it arrive?


References

  1. CSS Easing Functions Level 2 — W3C specification, “Cubic Bézier easing functions.”
  2. cubic-bezier() — MDN Web Docs reference.