Animation is one of the most effective tools in a UI designer's kit — and one of the easiest ways to accidentally tank your Lighthouse performance score. The good news: following a small set of rules eliminates almost all animation-related performance issues.
Rule 1 — Only animate composited properties
The GPU can animate two properties without triggering layout or paint: transform (translate, scale, rotate, skew) and opacity. Every other property — width, height, margin, padding, border, color, background — forces the browser to recalculate layout (expensive) or repaint pixels (less expensive but still measurable). If an animation feels janky, check DevTools Performance panel. If you see 'Layout' or 'Paint' events firing during the animation, you're animating the wrong property. Rethink the approach: instead of animating height from 0 to auto, animate scaleY from 0 to 1.
Rule 2 — Use will-change sparingly
will-change: transform tells the browser to promote an element to its own GPU layer in advance of an animation. This prevents jank on first render because the element is already composited. The trap: applying will-change to too many elements simultaneously saturates GPU memory, causing worse performance than no will-change at all. The rule: apply will-change only to elements that are actively about to animate, and remove it after the animation ends using JavaScript. Never apply will-change to more than 3–5 elements at once.
Rule 3 — prefer reduced motion
In 2026, ignoring @media (prefers-reduced-motion: reduce) is an accessibility violation. Users with vestibular disorders (motion sickness triggered by screen movement) and those who simply prefer less visual noise have this setting enabled. Always wrap non-essential animations in a reduced-motion check. In Tailwind: use motion-safe:transition-transform, motion-safe:animate-fade. In CSS: wrap @keyframes blocks inside @media (prefers-reduced-motion: no-preference) {}.
Rule 4 — CSS keyframes vs Web Animations API
CSS @keyframes run off-main-thread for composited properties — this is the ideal path for looping animations like spinners, pulse effects, and skeleton loaders. But CSS lacks programmatic control: you cannot easily pause, seek, or reverse a CSS animation based on scroll position or user interaction without JavaScript. For scroll-linked animations, interactive reveals, or state-driven transitions, use the Web Animations API (element.animate()) or GSAP/Framer Motion — they give you full imperative control while still running composited transforms off-thread.
Rule 5 — Keep animation durations honest
Micro-interactions: 150–250ms. Page transitions: 300–400ms. Hero entrances: 500–700ms. Anything longer than 700ms in a UI context starts feeling slow rather than polished. A 1200ms fade-in does not feel luxurious — it feels broken on a slow network. Use cubic-bezier easing (ease-out for entrances, ease-in-out for loops) rather than linear — linear motion reads as mechanical and unnatural to the human eye.
Practical checklist before shipping animations
Open Chrome DevTools → Performance → record a 3-second interaction → look for frames below 60fps (red bars in the frame timeline). Check: are you triggering layout? Check: are you animating on the main thread? Open the Layers panel and verify animated elements are their own GPU layer. Test with prefers-reduced-motion enabled in OS settings — ensure the page is still usable. Test on a real mid-range Android device (not just desktop). The rule of thumb: if it runs smoothly on a Redmi Note, it runs smoothly everywhere.
Nexova Team
Building X.IDE, Lean.x, and the tools Malaysian businesses need to grow online.