Cubic Bezier and Easing Functions in CSS
CSS animations and transitions use cubic Bezier curves to control timing. Understanding how these curves work lets you create animations that feel natural, bouncy, snappy, or anything in between.
The Basics
Every CSS transition has a timing function that maps time to progress. Given a normalized time value (0 to 1), the function returns how far along the animation should be (also 0 to 1).
.element {
transition: transform 0.3s ease-out;
/* ^^^^^^^^ timing function */
}A linear timing function means constant speed—50% of the time equals 50% of the progress. But real-world motion isn't linear. Objects accelerate and decelerate. This is where easing functions come in.
The cubic-bezier() Function
CSS provides the cubic-bezier() function for custom timing curves:
.element {
transition: transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
}The four numbers define two control points: cubic-bezier(x1, y1, x2, y2).
cubic-bezier(0.25, 0.1, 0.25, 1)The curve always starts at (0, 0) and ends at (1, 1). The two control points (P1 and P2, shown in red) shape the curve between them. The x-values represent time; the y-values represent progress.
How It Works
The cubic Bezier formula combines four points using basis functions (similar to Hermite interpolation, but with a different formulation):
P(t) = (1-t)³·P0 + 3(1-t)²t·P1 + 3(1-t)t²·P2 + t³·P3Where:
- P0 = (0, 0) — always the start
- P1 = (x1, y1) — first control point
- P2 = (x2, y2) — second control point
- P3 = (1, 1) — always the end
The browser evaluates this parametrically, then for any given time value, finds the corresponding progress value on the curve.
The Bernstein Basis Functions
The cubic Bezier curve uses Bernstein polynomials as its basis functions:
Each basis function determines how much its corresponding control point influences the curve at any given t:
- B₀ = (1-t)³ — Weight of P0 (start point). Strongest at t=0, fades to zero.
- B₁ = 3(1-t)²t — Weight of P1 (first control). Peaks around t≈0.33.
- B₂ = 3(1-t)t² — Weight of P2 (second control). Peaks around t≈0.67.
- B₃ = t³ — Weight of P3 (end point). Zero at start, strongest at t=1.
Notice that at any value of t, the four functions sum to exactly 1. This ensures the curve stays bounded by its control points.
Easing Functions Visualized
linearcubic-bezier(0.25, 0.1, 0.25, 1.0)cubic-bezier(0.42, 0, 1, 1)cubic-bezier(0, 0, 0.58, 1)cubic-bezier(0.42, 0, 0.58, 1)cubic-bezier(0.2, 0, 0, 1)cubic-bezier(0.34, 1.56, 0.64, 1)cubic-bezier(0.68, -0.55, 0.27, 1.55)The linear() Function (CSS Level 2)
Modern CSS adds linear() for complex multi-stop easing. Unlike cubic-bezier() which is limited to smooth curves with two control points, linear() lets you define arbitrary curves by specifying a series of output values.
The Syntax
transition-timing-function: linear(output1, output2, output3, ...);Each value represents the output (progress) at evenly-spaced input (time) points. The browser linearly interpolates between them.
For example:
linear(0, 0.25, 0.5, 0.75, 1)This defines 5 points. Since there are 5 values, they're placed at:
0at t=0% (start)0.25at t=25%0.5at t=50%0.75at t=75%1at t=100% (end)
This happens to be a straight line—the same as linear. But you can use any values:
linear(0, 0.5, 0.5, 1) /* pause in the middle */
linear(0, 1, 0, 1) /* bounce back and forth */
linear(0, 1.2, 1) /* overshoot */Explicit Stop Positions
You can also specify where each point occurs:
linear(0, 0.5 30%, 1)This places the middle value at 30% instead of 50%. Values without positions are distributed evenly between their neighbors.
The Math: Linear Interpolation
Between any two adjacent points, the browser uses simple linear interpolation:
output = y0 + (y1 - y0) × ((t - t0) / (t1 - t0))Where:
tis the current time (0 to 1)t0,t1are the time positions of the surrounding pointsy0,y1are the output values at those points
For example, with linear(0, 0.8, 1):
- At t=0.25: We're between point 0 (t=0, y=0) and point 1 (t=0.5, y=0.8)
- Output = 0 + (0.8 - 0) × (0.25 / 0.5) = 0.4
Writing Your Own
To create a custom linear() easing:
- Define your mathematical function (e.g., a bounce, spring, or elastic curve)
- Sample it at regular intervals (more samples = smoother curve)
- Output the values as a comma-separated list
Here's JavaScript to generate a bounce curve:
function generateBounce(samples = 20) {
const values = [];
for (let i = 0; i <= samples; i++) {
const t = i / samples;
// Simple bounce: parabola that hits 1 at t=0.7, then settles
let y;
if (t < 0.7) {
y = (t / 0.7) ** 0.5; // ease-out to peak
} else {
const bounceT = (t - 0.7) / 0.3;
y = 1 - 0.2 * Math.sin(bounceT * Math.PI); // small bounce
}
values.push(y.toFixed(3));
}
return `linear(${values.join(", ")})`;
}For physics-based animations, you can sample real spring/bounce equations:
function generateSpring(samples = 20, stiffness = 100, damping = 10) {
const values = [];
for (let i = 0; i <= samples; i++) {
const t = i / samples;
// Damped harmonic oscillator approximation
const decay = Math.exp(-damping * t);
const oscillation = Math.cos(stiffness * t * 0.1);
const y = 1 - decay * oscillation;
values.push(Math.max(0, Math.min(1.5, y)).toFixed(3));
}
return `linear(${values.join(", ")})`;
}cubic-bezierlinear()linear()The first curve shows what cubic-bezier() can do—a single overshoot. The bounce and spring curves show what linear() enables: multiple oscillations that would be impossible with just two control points.
Bounce Effect
transition-timing-function: linear(
0,
0.004,
0.016,
0.035,
0.063,
0.098,
0.141,
0.191,
0.25,
0.316,
0.391,
0.473,
0.563,
0.66,
0.766,
0.879,
1,
0.891,
0.813,
0.766,
0.75,
0.766,
0.813,
0.891,
1
);This 25-point curve creates a bounce effect. The values rise to 1 at the 17th point (around t≈0.67), then oscillate: 0.891 → 0.75 → 0.891 → 1. Each bounce is smaller than the last.
Spring Physics
transition-timing-function: linear(
0,
0.009,
0.035,
0.078,
0.141,
0.223,
0.324,
0.446,
0.586,
0.746,
0.923,
1.114,
1.032,
0.984,
0.969,
0.984,
1.016,
1.008,
0.996,
1.002,
1
);This 21-point curve simulates a damped spring. Notice value 12 is 1.114—it overshoots by 11.4%. Then it oscillates around 1 with decreasing amplitude: 1.032 → 0.969 → 1.016 → 0.996 → 1.002 → 1.
Performance Note
The timing function itself has no performance impact—it's just a mathematical formula evaluated once per frame. What matters is what you're animating:
GPU-accelerated (fast):
transformopacity
Triggers layout (slow):
width,heighttop,left,right,bottommargin,padding
Always prefer transform: translateX() over left for position animations.
Tools
- cubic-bezier.com — Interactive curve editor with comparison
- easings.net — Gallery of common easing functions
- Chrome DevTools — Edit timing functions visually in the Styles panel
See Also
- Hermite Basis Interpolation — Alternative basis functions used for curve interpolation
- Procedural Ink Splotches with SVG — Using interpolation for procedural graphics