CSS Timing Functions and Cubic-Bezier Explained

This article provides an overview of CSS timing functions, explaining how they control the rate of change during transitions and animations. You will learn the mechanics behind the cubic-bezier() function, how its four coordinates govern acceleration and deceleration along a curve, and how these mathematical models translate into natural, physics-driven web motion.

What Are CSS Timing Functions?

CSS timing functions determine how an intermediate property value transitions from its start state to its end state over a set duration. Applied via properties like transition-timing-function or animation-timing-function, they do not alter the total time an animation takes, nor do they change the starting and ending values. Instead, they dictate the velocity of the transition throughout its execution.

Without a timing function specifying varying speed, properties update in a purely mechanical way. By manipulating acceleration, timing functions allow elements to mimic real-world physics, such as inertia, friction, and gravity.

The Coordinate System of cubic-bezier()

The mathematical foundation of smooth web animations is the cubic Bézier curve, represented in CSS as cubic-bezier(x1, y1, x2, y2). This function plots animation progress on a two-dimensional coordinate plane:

  • X-Axis (Time): Represents the progression of time from 0 (0% duration) to 1 (100% duration). Because time cannot flow backward, x1 and x2 must always fall within the range of 0 to 1.
  • Y-Axis (Output Progress): Represents the progression of the animated property from 0 (start value) to 1 (end value). Unlike the time axis, the Y-axis can extend below 0 or above 1, allowing elements to overshoot their target values for spring or bounce effects.

A cubic Bézier curve relies on four anchor points:

  1. P0 (0, 0): The fixed starting point in both time and property state.
  2. P1 (x1, y1): The first control handle, pulling the curve from the start.
  3. P2 (x2, y2): The second control handle, pulling the curve toward the finish.
  4. P3 (1, 1): The fixed ending point in both time and property state.

When you write cubic-bezier(x1, y1, x2, y2), you are specifying the coordinates of control points P1 and P2.

How the Curve Shapes Acceleration

The slope of the curve at any given point represents the velocity of the animated element:

  • Steep Slope: Indicates rapid progression over a short period of time (high speed/acceleration).
  • Flat Slope: Indicates minimal progression over time (low speed/deceleration).
  • Negative Slope: Occurs when an element moves backward relative to its start or target position.

By pulling the control handles, you reshape the curve and dictate how speed changes:

  • Ease-In (cubic-bezier(0.42, 0, 1, 1)): The curve starts flat and steepens toward the end. The animation begins slowly and accelerates until it abruptly hits the final state, mimicking a falling object.
  • Ease-Out (cubic-bezier(0, 0, 0.58, 1)): The curve starts steep and levels off near the top. The animation begins at maximum speed and gradually decelerates to a stop, mimicking a car braking.
  • Ease-In-Out (cubic-bezier(0.42, 0, 0.58, 1)): The curve starts flat, steepens in the middle, and flattens out at the end. The element accelerates out of its resting state and smoothly decelerates before settling.

Standard Keywords vs. Custom Curves

CSS includes predefined keyword aliases that represent standard cubic Bézier curves:

  • linear: Equivalent to cubic-bezier(0, 0, 1, 1). The slope is constant, meaning the element moves at the exact same speed from start to finish.
  • ease: Equivalent to cubic-bezier(0.25, 0.1, 0.25, 1). The default timing function; it accelerates quickly and decelerates gradually.
  • ease-in: Equivalent to cubic-bezier(0.42, 0, 1, 1).
  • ease-out: Equivalent to cubic-bezier(0, 0, 0.58, 1).
  • ease-in-out: Equivalent to cubic-bezier(0.42, 0, 0.58, 1).

Custom curves expand beyond these basic presets. By setting y2 higher than 1 (for instance, cubic-bezier(0.34, 1.56, 0.64, 1)), the property value temporarily exceeds 100% before snapping back to 1, creating an elastic overshoot effect. Conversely, setting y1 below 0 pulls the element in reverse before it lunges forward.