A CSS animation is janky on mobile devices—how do you identify and fix the issue
Profile first: record the animation in Chrome DevTools Performance panel (with CPU throttling), look for long frames, purple Layout / green Paint bars. Jank usually means you're animating layout/paint properties. Fix by switching to `transform`/`opacity`, promoting the element with `will-change`, reducing paint area, debouncing JS-driven animation, and respecting `prefers-reduced-motion`.
A janky animation = the browser can't finish a frame within its budget (~16.7ms at 60fps), so frames drop. The approach is measure → diagnose → fix → verify — never guess.
Step 1 — Reproduce and measure
- Test on a real mid-range device or use DevTools CPU throttling (4–6×) and network throttling — desktop hides mobile jank.
- Open DevTools → Performance, record the animation.
- Look at the frames track: long frames (>16ms) are jank. Inspect them:
- Purple bars = Layout (reflow) — you're animating a layout property.
- Green bars = Paint — large or expensive paint areas.
- Yellow = scripting — JS is the bottleneck.
- The Rendering tab: enable "Paint flashing" (see what repaints) and "Layer borders."
Step 2 — Common causes of mobile jank
- Animating layout/paint properties —
width,height,top,left,margin,box-shadow. Each frame triggers reflow/repaint on the main thread. - Large paint areas — animating something that forces a big region (or the whole page) to repaint.
- Too many / too few compositor layers — none means no GPU offload; too many exhausts mobile GPU memory.
- JS-driven animation on the main thread, blocked by other work.
- Heavy effects — big
blur(), complexbox-shadow, many gradients — GPUs on phones are weak. - Layout thrashing — reading layout (
offsetHeight) inside the animation loop.
Step 3 — Fixes
- Animate
transformandopacityonly — compositor-only, GPU, off-main-thread. ReplaceleftwithtranslateX,widthwithscale. - Promote the element:
will-change: transform(ortransform: translateZ(0)) — sparingly, remove after. - Shrink the paint area — animate a small element, not a full-width container; use
contain: paint. - Move JS animation off the main thread — use CSS transitions/animations or the Web Animations API instead of
setInterval; or use a Web Worker for the computation. - Reduce effect cost — smaller blur radius, simpler shadows, fewer animated nodes.
- Use
requestAnimationFramefor any JS-driven animation, neversetTimeout. - Respect
prefers-reduced-motion— disable or simplify for users who opt out (also an accessibility win).
Step 4 — Verify
Re-record. Frames should be green and under 16ms. Check on the actual device, not just throttled desktop.
Senior framing
The senior answer leads with "profile, don't guess" — name the DevTools workflow and what the colored bars mean. Then the fix is the rendering-pipeline insight: jank = reaching Layout/Paint per frame, so move to compositor-only properties. Mentioning mobile-specific realities (weak GPUs, layer-memory limits, CPU throttling to simulate) and prefers-reduced-motion shows you've actually debugged this, not just read about transform.
Follow-up questions
- •What do the purple and green bars in the Performance panel mean?
- •Why test with CPU throttling, and what multiplier?
- •How can too many compositor layers hurt mobile performance?
Common mistakes
- •Guessing at the fix instead of profiling.
- •Testing only on a fast desktop.
- •Animating layout properties and adding will-change as a 'fix' without changing the property.
- •Leaving will-change on every element permanently.
Edge cases
- •Compositor-only animations can still jank if the layer is huge (lots of texture memory).
- •iOS Safari handles will-change and layers differently than Chrome.
- •prefers-reduced-motion should be honored regardless of performance.
Real-world examples
- •Slide-in drawers, parallax, carousels, scroll-linked animations on mobile.