All posts

Playbook · Performance

How to find animations that repaint an idle page

Screen-record a product demo and the video stutters. The page isn’t doing anything. That’s the clue.

In September it happened to us on the Axelize app. A page nobody was touching was redrawing itself 60 times a second. The same class of problem then showed up in the animated call scenes on the Axelize blog, which is why I’m writing this down. Axelize is the sales assistant I build.

The first loop: a caret that never stopped

The cause was a custom caret animation on our text input. It was set to run forever on every input, focused or not. So any page with a search box redrew 60 times a second for as long as it stayed open.

On the company drawer it hurt most. A blurred full-screen backdrop made each of those frames about 38% more expensive. The fix was one move: the animation now runs only while the input has focus.

A sheen effect on cards had the same shape. It animated background-position under a blur, so the browser repainted every frame. It’s now a wide pseudo-element that slides with transform. It looks pixel-identical in Chrome, Firefox and Safari.

The rule behind it

web.dev says only two properties are handled by the compositor alone: transform and opacity. Animating anything else takes more work and can look janky on a low-end phone (web.dev, last updated in 2015). That page is old. In our traces the rule still held.

How to run the check

Step 1

Leave the page alone

Load it, then touch nothing. No hover, no scroll, no focus. You’re measuring a page that’s supposed to be asleep.

Step 2

Record a trace

We drive Chrome from Playwright and start a trace over the DevTools Protocol, with the frame and compositor categories on. Three seconds of idle is plenty.

Step 3

Count the frames

Count DrawFrame events and add up the Paint time. A sleeping page shows close to nothing. If you count 180 frames in three seconds, that’s 60 a second, and something is looping.

Step 4

Find the loop

List every animation that never ends. Check which properties each one moves. Anything other than transform or opacity is a suspect, and height, width and background-position are the usual ones.

Step 5

Add a guard

Write a test that fails if a page left alone runs an infinite animation on anything but transform, translate, scale, rotate or opacity. We watched ours fail on both old bugs before we trusted it.

What it did to the articles

On 7 October we ran the same sweep across all seven articles on a production build. We traced each page for 1.5 seconds at every 700 pixels of scroll and added up the events.

Trace events, summed over the sweepBeforeAfter
Layout1,5820
Paint4,90691

Two things caused it. The bars in the call scenes animated height, which forces a layout on every frame. They now scale from the bottom. A glowing beam animated a custom property on a masked gradient, which repainted every frame. Now a pseudo-element rotates under the mask.

What’s left at idle is a few opacity pulses and the video tiles that are actually playing.

One honest limit. We never caught a single dropped frame on an M-series Mac at 2x scale, before or after. What we removed was main-thread work, which is the kind a slower phone feels first.

Let's build something worth shipping

One flat monthly fee. Unlimited requests. Design and engineering under one roof — pause or cancel whenever you like.