The Mathematics Behind CSS Animations

Mathematical equations overlaid on flowing animation curves

CSS animations might seem like magic, but underneath they're driven by elegant mathematical principles. Understanding these foundations will help you create more sophisticated and performant animations.

The Geometry of Easing Functions#

Every CSS animation relies on easing functions to control the rate of change over time. The most common are cubic Bézier curves.

Cubic Bézier Curves#

A cubic Bézier curve is defined by four control points. For CSS, the curve is constrained to start at (0,0)(0, 0) and end at (1,1)(1, 1), so we only specify the two middle control points:

B(t)=(1t)3P0+3(1t)2tP1+3(1t)t2P2+t3P3B(t) = (1-t)^3 P_0 + 3(1-t)^2 t P_1 + 3(1-t) t^2 P_2 + t^3 P_3

Where tt ranges from 0 to 1, and P0=(0,0)P_0 = (0,0), P3=(1,1)P_3 = (1,1).

In CSS, we write this as:

CSS
.element {
    transition: transform 0.3s cubic-bezier(0.4, 0.0, 0.2, 1);
}

The values (0.4, 0.0, 0.2, 1) represent the coordinates of P1P_1 and P2P_2.

Common Easing Functions#

The built-in CSS easing functions are actually specific Bézier curves:

NameBézier ValuesEquation Behavior
ease(0.25, 0.1, 0.25, 1.0)Slow start, fast middle, slow end
ease-in(0.42, 0, 1.0, 1.0)Slow start: v(t)t2v(t) \propto t^2
ease-out(0, 0, 0.58, 1.0)Slow end: v(t)tv(t) \propto \sqrt{t}
ease-in-out(0.42, 0, 0.58, 1.0)Slow start and end

Trigonometry in Animation#

Trigonometric functions create natural, oscillating motion that's perfect for breathing effects, waves, and pendulum-like animations.

Sine Waves#

The sine function produces smooth oscillation:

y(t)=Asin(ωt+ϕ)y(t) = A \sin(\omega t + \phi)

Where:

  • AA is the amplitude (how far it moves)
  • ω\omega is the angular frequency (ω=2πf\omega = 2\pi f)
  • ϕ\phi is the phase offset

In CSS, we can approximate this with keyframes:

CSS
@keyframes breathe {
    0%, 100% { transform: scale(1); }
    50% { transform: scale(1.1); }
}

For more precise control, use JavaScript:

JavaScript
function animate(element, amplitude, frequency) {
    const omega = 2 * Math.PI * frequency;
    
    function update(time) {
        const t = time / 1000;
        const y = amplitude * Math.sin(omega * t);
        element.style.transform = `translateY(${y}px)`;
        requestAnimationFrame(update);
    }
    
    requestAnimationFrame(update);
}

Circular Motion#

For circular paths, we use parametric equations:

x(t)=rcos(ωt)x(t) = r \cos(\omega t)y(t)=rsin(ωt)y(t) = r \sin(\omega t)

This creates perfect circular motion with radius rr and angular velocity ω\omega.

Physics-Based Animation#

The most realistic animations simulate physical forces.

Spring Dynamics#

A spring follows Hooke's Law combined with damping:

F=kxcvF = -kx - cv

Where:

  • kk is the spring constant (stiffness)
  • xx is displacement from equilibrium
  • cc is the damping coefficient
  • vv is velocity

The solution for an underdamped spring is:

x(t)=Aeγtcos(ωdt+ϕ)x(t) = A e^{-\gamma t} \cos(\omega_d t + \phi)

Where γ=c2m\gamma = \frac{c}{2m} is the damping ratio and ωd=ω02γ2\omega_d = \sqrt{\omega_0^2 - \gamma^2} is the damped frequency.

JavaScript
function springAnimation(stiffness, damping, mass) {
    const omega0 = Math.sqrt(stiffness / mass);
    const gamma = damping / (2 * mass);
    const omegaD = Math.sqrt(omega0 ** 2 - gamma ** 2);
    
    return function(t, initialDisplacement) {
        return initialDisplacement * 
            Math.exp(-gamma * t) * 
            Math.cos(omegaD * t);
    };
}

Gravity and Projectile Motion#

For falling or bouncing objects:

y(t)=y0+v0t12gt2y(t) = y_0 + v_0 t - \frac{1}{2}gt^2

Where g9.8m/s2g \approx 9.8 \, \text{m/s}^2 is gravitational acceleration.

Interpolation Mathematics#

Smooth transitions between values use interpolation functions.

Linear Interpolation (Lerp)#

The simplest interpolation:

lerp(a,b,t)=a+(ba)t=a(1t)+bt\text{lerp}(a, b, t) = a + (b - a) \cdot t = a(1-t) + bt
JavaScript
const lerp = (a, b, t) => a + (b - a) * t;

Smoothstep#

For smoother transitions, we use polynomial interpolation:

smoothstep(t)=3t22t3\text{smoothstep}(t) = 3t^2 - 2t^3

This has zero derivatives at t=0t = 0 and t=1t = 1, creating smooth acceleration and deceleration.

An even smoother variant is smootherstep:

smootherstep(t)=6t515t4+10t3\text{smootherstep}(t) = 6t^5 - 15t^4 + 10t^3
JavaScript
const smoothstep = t => t * t * (3 - 2 * t);
const smootherstep = t => t * t * t * (t * (6 * t - 15) + 10);

Practical Application: Custom Easing#

Let's create a bounce easing function. The math involves a decaying sinusoid:

bounce(t)=1cos(nπt)ekt\text{bounce}(t) = 1 - |cos(n \pi t)| \cdot e^{-kt}
JavaScript
function bounceEasing(t, bounces = 4, decay = 6) {
    const n = bounces;
    const k = decay;
    return 1 - Math.abs(Math.cos(n * Math.PI * t)) * Math.exp(-k * t);
}

Conclusion#

Understanding the mathematics behind animations transforms you from someone who copies easing values to someone who crafts intentional motion. Whether you're using Bézier curves for subtle UI feedback or physics simulations for game-like interactions, these mathematical foundations are your toolkit for creating exceptional user experiences.

The beauty of animation math is that it mirrors the natural world—springs, pendulums, and waves all follow these same equations, which is why mathematically-driven animations feel so satisfying and natural.

Share:

Related Articles