Our site has WebGL smoke, a canvas full of orbits, infinite marquees, twinkling stars and a grainy background. And it still runs smooth. It wasn't free: along the way we went from 60fps to 5 (yes, five) and came back. Here's what we learned, names and all, so it doesn't happen to you.
The golden rule: transform and opacity, nothing else
The browser paints in layers. Animating transform or opacity only moves layers that are already painted (the GPU handles it, for free). Animating almost anything else (filter, box-shadow, width, background-position over large areas) forces a repaint on every frame. One small element gets a pass; twenty elements don't.
The review stars "breathed" by animating filter: drop-shadow. Every visible star was re-rasterized 60 times per second. With ~25 stars on screen: noticeable stuttering. Now they breathe with transform: scale() and the glow is static. Identical look, zero cost.
mix-blend-mode and filter: the silent killers
The first version of our hero used mix-blend-mode for the noise and filter: blur(90px) for the glows, on top of a canvas that repaints every frame. Result: 5fps. The blend forces the whole screen to recompose and the blur re-blurs on every repaint. The fix: glows with pre-blurred radial-gradient (no filter) and zero blend modes over anything animated. From 5fps to 40+ with the same look.
The rAF loops nobody sees
We had a ball of light that followed the cursor with smooth interpolation. Pretty, and a requestAnimationFrame loop running always, even when the mouse had been still for a minute. Every continuous effect has to answer three questions: does it pause off screen? (IntersectionObserver), does it pause when the tab is hidden? (visibilitychange), does it stop when idle? If any answer is no, it's stealing battery.
- Hero canvas: pauses when it leaves the viewport and when the tab is hidden
- CTA WebGL smoke: same, plus DPR capped at 1.5 and automatic resolution downscaling if it detects slow frames
- Megamenu videos: preload=none and they only play while the panel is open
backdrop-filter: you pay on every scroll
A header with backdrop-filter: blur(16px) re-blurs whatever's behind it on every scroll frame. We dropped to 12px and lightened the background: same glass, smaller bill. If your site "stutters when you scroll", start here.
/* MAL: repinta cada frame */
.star { animation: glow 2s infinite; }
@keyframes glow { 50% { filter: drop-shadow(0 0 9px gold); } }
/* BIEN: compositor puro, mismo efecto */
.star { filter: drop-shadow(0 0 5px gold); /* estático */ }
@keyframes glow { 50% { transform: scale(1.1); } }How we measured it
No intuition: DevTools → Performance with CPU x4, the Paint flashing layer turned on (if something flashes green during an animation, you're repainting), and real recordings on a modest laptop. The site has to run smooth on your client's computer, not yours.
An animation that drops below 60fps isn't an effect: it's a bug with glitter on it.
Pablo Huget, Web Developer at Intervolutions