Home / Articles / Animation & Motion /

animation

Easing functions explained

The difference between animation that feels mechanical and animation that feels alive.

Why easing matters

Linear animation — constant speed from start to finish — feels robotic. Real objects accelerate and decelerate. Easing functions map time to progress on a curve, making motion feel physical. The right easing is often the difference between a UI that feels polished and one that feels like a prototype.

The built-in keywords

/* ease — slow start, fast middle, slow end (default) */
transition: transform 0.3s ease;

/* ease-in — slow start, fast end (entering the screen) */
transition: opacity 0.3s ease-in;

/* ease-out — fast start, slow end (most natural for UI) */
transition: transform 0.3s ease-out;

/* ease-in-out — slow start and end */
transition: transform 0.3s ease-in-out;

/* linear — constant speed (rarely what you want for UI) */
transition: width 0.3s linear;

For most UI transitions, ease-out is the best default — it responds immediately to input and decelerates to a stop, which matches how users expect objects to behave.

cubic-bezier() for custom curves

All keyword easings are shortcuts for cubic-bezier() curves. You define four control points that shape the timing:

/* Snappy — fast start, abrupt stop */
transition: transform 0.2s cubic-bezier(0.25, 0, 0, 1);

/* Spring-like overshoot */
transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);

/* Smooth deceleration */
transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1);

The spring-like cubic-bezier(0.34, 1.56, 0.64, 1) is popular for toggles, checkboxes, and micro-interactions where a slight bounce adds energy.

linear() for complex curves

The linear() function is new in 2024–2025 and lets you define multi-point easing — essentially any curve shape:

/* Bounce easing — impossible with cubic-bezier alone */
transition: transform 0.6s linear(
  0, 0.36, 0.66, 0.89,
  1, 0.97, 1, 0.99, 1
);

/* Spring with damping */
transition: transform 0.5s linear(
  0, 0.22, 0.68, 1.12,
  0.98, 1.01, 1
);

linear() is supported in Chrome 113+, Safari 17.2+, and Firefox 112+. It enables bounce, spring, and elastic effects that previously required JavaScript animation libraries.

steps() for frame-by-frame

Use steps() for sprite animations, typewriter effects, or any animation that should jump between discrete frames rather than interpolating smoothly:

/* Typewriter: reveal text character by character */
.typewriter {
  animation: typing 3s steps(30, end);
}

/* Sprite sheet: 8-frame walk cycle */
.sprite {
  animation: walk 0.8s steps(8) infinite;
}

Choosing the right easing

  • UI transitions (hover, focus, state change): ease-out or cubic-bezier(0, 0, 0.2, 1)
  • Elements entering the screen: ease-out
  • Elements leaving the screen: ease-in
  • Micro-interactions (toggles, checkboxes): spring cubic-bezier
  • Scroll-driven animations: linear (progress should match scroll position)
  • Loading/progress bars: linear
Set a project-wide default: * { transition-timing-function: cubic-bezier(0, 0, 0.2, 1); } gives every transition a natural deceleration.