Key takeaways
- Animate only
transformandopacityfor smooth, cheap animations. - Animating
width,height,topormarginforces layout work on every frame and can cause jank and layout shift. - Respect
prefers-reduced-motionand keep animations short and purposeful.
Subtle motion makes interfaces feel polished: a button that lifts on hover, a menu that slides in, a theme toggle where the sun turns into a moon. But poorly built animations drain batteries, stutter on mid-range phones and can even hurt Core Web Vitals. Here’s how to animate well.
How browsers render a frame#
- Style — work out which CSS applies.
- Layout — calculate sizes and positions.
- Paint — fill in pixels.
- Composite — combine layers on the GPU.
Changing width triggers all four steps. Changing transform or opacity usually only needs compositing, which the GPU handles easily at 60 frames per second.
Transitions vs keyframe animations#
/* Transition: animate between two states */
.card { transition: transform .3s ease, box-shadow .3s ease; }
.card:hover { transform: translateY(-4px); box-shadow: 0 12px 30px rgba(0,0,0,.15); }
/* Keyframes: multi-step or looping animation */
@keyframes pulse { 50% { opacity: .5; } }
.dot { animation: pulse 2.4s ease-in-out infinite; }
Cheap vs expensive properties#
| Animate this | Instead of |
|---|---|
transform: translateX() |
left / margin-left |
transform: scale() |
width / height |
opacity |
visibility toggles with layout changes |
transform: scaleX() on a bar |
Animating width of a progress bar |
This site’s reading progress bar uses transform: scaleX() for exactly this reason.
Need a hand with this?
Get a quick, honest review of your website’s SEO.
Respect reduced-motion preferences#
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: .01ms !important;
transition-duration: .01ms !important;
scroll-behavior: auto !important;
}
}
Some people experience nausea or dizziness from motion. Honouring their system setting is an accessibility requirement (WCAG 2.3.3).
Animations and Core Web Vitals#
- Layout-changing animations on page load can count as layout shift.
- Heavy JavaScript animation libraries add to main-thread work and can hurt INP.
- Fade-in-on-scroll effects that start content at
opacity: 0can delay LCP if applied to the hero — keep the first screen visible immediately.
Practical guidelines#
- Durations: 150–300 ms for UI feedback; up to ~500 ms for larger transitions.
- Easing:
ease-outor a customcubic-bezier(.2,.8,.2,1)feels natural for entering elements. - Use
will-changesparingly, only on elements about to animate. - Prefer CSS over JavaScript for simple effects; use the Web Animations API or View Transitions for more complex ones.
View Transitions#
The View Transitions API animates between page states — or even between pages on multi-page sites — with a cross-fade by default. This theme uses it for a smooth light/dark toggle in browsers that support it, falling back instantly elsewhere.
CSS animations FAQ#
Do animations hurt SEO?#
Not directly, but heavy or layout-shifting animations can worsen Core Web Vitals and user experience.
Should I use an animation library?#
For simple hover and entrance effects, plain CSS is enough. Libraries like GSAP are great for complex, timeline-based animation.
Why does my animation stutter on phones?#
Usually because it animates layout properties or large shadows/filters. Switch to transform and opacity.