Field Notes
Our animated drawings burned a CPU core doing nothing
The technical drawings on this site now move. Work travels through each figure as a packet that swells the line it rides, a reviewer's finding goes back round the loop in red, a check stamps, a person's decision ripples. Fourteen figures, each on its own eight-to-ten-second cycle, written as SVG animation so they play without any JavaScript at all.
Before shipping the next change we measured what they cost a visitor's machine. They were taking a quarter to a third of a CPU core, all the time, including when none of them was on screen.
How we measured
Chrome reports how long its main thread has been busy through the DevTools Protocol. Read it, wait, read it again, and the difference over the wait is the share of one core the page is using while it sits there. We ran it headless at 1440 by 900 against the built site, with nothing else happening on the page.
const cdp = await page.context().newCDPSession(page);
await cdp.send('Performance.enable');
const busy = async () => (await cdp.send('Performance.getMetrics'))
.metrics.find((m) => m.name === 'TaskDuration').value;
const a = await busy();
await page.waitForTimeout(10_000);
const b = await busy();
console.log(((b - a) / 10) * 100, '% of the main thread');
A page with no figures, the privacy policy, read 0%. The home page read 25% with Fig. 0 in view, and 27% scrolled to the bottom, where no figure is visible. The case studies page, with six figures, read 34 to 37%.
Why it cost anything off screen
SVG animation (the <animate> element, usually called SMIL) runs on the document's clock. The browser samples every animation on every frame, whether or not the element is visible, and whether or not its value is changing. Most of our timeline is held values: a mark that is off for eight seconds and on for one still gets sampled sixty times a second for all nine.
The first fix was the obvious one: pause each figure's clock while it is off screen, with an IntersectionObserver and the SVG element's own pauseAnimations(). Off screen went to 0%. On screen, Fig. 0 still took 15%.
Then a control: we hid every moving layer and measured again. Nothing visible was changing, and the figure still took 8%. That is the cost of the clock itself.
What holding still costs
To separate the clock from the drawing we built two minimal pages. A hundred marks that each blink once per ten-second cycle cost 4.9% as SVG animation and 0.1% as CSS animation. Thirty lines whose dash offset moves cost 6.3% as SVG animation and 6.1% as CSS. Moving costs the same in either; holding still is almost free in one and not in the other.
So the cost to remove was all the frames in which nothing changes.
The player
We kept the figures written as SVG animation, because that plays without JavaScript and under a strict content security policy. A small script then takes over where it can. It reads each animation's values and key times, removes the element so the document clock has nothing to sample, and keeps only the stretches where the value actually changes:
// The stretches of a timeline where the value changes.
export function segments(values, keyTimes, T) {
const out = [];
for (let i = 0; i < values.length - 1; i++) {
if (values[i] === values[i + 1]) continue;
out.push({ t0: keyTimes[i] * T, t1: keyTimes[i + 1] * T, from: values[i], to: values[i + 1] });
}
return out;
}
Each stretch is started at its moment as a one-shot Web Animation, which ends on its final value and stops. Between moments nothing runs. The figure plays only while it is on screen, resumes where it left off, and does not play at all for visitors who ask for reduced motion.
With the player, Fig. 0 on screen took 9 to 11% across repeated runs, a figure on the case studies page 7 to 10%, and anything off screen 0%. The player adds about a kilobyte to the page once compressed; the site's JavaScript is 1.4 KB on the wire.
What is left, and what we did not do
The remaining cost is painting the packets while they move, and it is overstated here: headless Chrome draws in software, and a visitor's browser draws on the GPU. We tried stepping the constant traffic in Fig. 0's fibres at seven updates a second instead of every frame; it measured no better, so we kept it smooth.
We did not rewrite the figures as canvas or WebGL. They are drawings with labels a screen reader can read and a search engine can index, and they have to stay that way.
The general lesson
Motion on a page has a running cost that load-time metrics are not built to show: Lighthouse measures a page while it loads, and scored these pages 100 for performance after the change. Measure the page sitting still, off screen and on, and measure a control that isolates the thing you suspect. The number that mattered here was the 8% with nothing visible moving.