Blog
Motion the visitor does not pay for
Animation is a performance decision before it is an aesthetic one, and most sites settle the bill on the visitor's phone.
Published
Someone opens a company website on a phone they bought three years ago, waiting for a bus. The page has loaded. The text is there. But as their thumb moves, the scroll catches and stutters, as though something behind the screen is thinking.
The thing thinking is the animation that was added to make the site feel considered. It felt considered on the machine of the person who built it.
Every effect on a page is paid for by somebody, and the person paying is not the one who added it. It is the visitor's device. So when we built this site, we treated motion as a performance decision first and an aesthetic one second.
What the browser actually does with a frame
Once you know the order of operations, the question "is this effect expensive?" stops being a matter of taste.
There are four stages. Style works out which CSS values apply to each element. Layout works out where everything sits and how large it is. Paint fills in the pixels — colours, borders, shadows. Composite stacks the painted layers into the final image.
Animate width, height, top, left or margin and the browser must redo layout and paint on every single frame, sixty times a second, in the two most expensive stages of the whole sequence.
Animate transform or opacity and the work can be handed to the compositor, the last and cheapest stage. The layer is already painted. All that remains is moving it.
This is why the three aurora blobs on this site only ever translate and scale. They never change width and never change colour. Each carries a blur(70px) filter, which is genuinely expensive to compute — but it is computed once. After that the blurred result is cached, and sliding a cached image around costs close to nothing.
Expensive and heavy are not the same property. Something computed once and then moved behaves completely differently from something recomputed every frame.
Scroll animation with no JavaScript at all
Content that fades up as it enters the viewport is usually built with JavaScript — an IntersectionObserver, or worse, a scroll listener.
This site uses neither. It uses animation-timeline: view(), which ties the animation directly to the element's own progress through the viewport. No observer, no listener, no work on the main thread — the same thread that has to answer the visitor's taps and keystrokes.
In a browser that does not support it, the content simply renders where it is. Nothing breaks, nothing is hidden. It degrades to "visible", which is the correct failure.
Compare that to the common pattern: hide the content at opacity zero, then let a script reveal it. If the script fails to load, the visitor gets a blank page. That is the wrong failure, and it is one bad network away at all times.
Why a scroll listener is a poor default
Scroll events fire more often than the screen refreshes. Your handler runs more times than there are frames for anyone to see, so a share of that work cannot become anything visible by definition.
Worse, if the handler reads a position — getBoundingClientRect, offsetTop — the browser is forced to recalculate layout on the spot to return an accurate answer. That is a synchronous reflow: everything stops to take a measurement, at the exact moment the visitor's thumb is moving.
A loop that stops itself
The light that follows the cursor here runs on one pointermove listener and one requestAnimationFrame loop.
What matters is not the count but the exit condition. Each frame, the light moves a fraction of the way toward the pointer, then checks whether it has caught up. If it is still behind, it schedules another frame. If it has arrived, it schedules nothing, and the loop ends.
The result is that a page nobody is touching runs nothing at all. Not a little — nothing. Treat that as the default: an idle page should cost zero. A tab left open in the background has no business draining anyone's battery.
And the layout read that costs so much elsewhere appears exactly once in that listener — a single getBoundingClientRect, taken only on the card actually under the cursor, not on every card on the page.
Not billing devices that could never use it
Before anything is created, the code asks one question: does this device match (hover: hover) and (pointer: fine)?
A phone does not. There is no cursor to follow; the effect is meaningless under a thumb. So on a phone it is never created in the first place.
The distinction is subtle and it matters. Hiding an effect with a media query means the code still ships, still runs, still binds its listeners, still holds memory — the visitor simply cannot see the result. They pay for something they will never receive. Checking first and doing nothing means there is no bill at all.
The same check covers prefers-reduced-motion. A visitor who has asked their operating system for less movement does not get a quieter version of the effect. They get no effect, because none was built.
Why 23, 31 and 41 seconds
The three aurora blobs cycle over 23, 31 and 41 seconds. Those numbers are not arbitrary, and they were not chosen for being odd.
Suppose they were 20, 30 and 40. Every 120 seconds all three would return to exactly the same arrangement together. Anyone who stays on the page long enough starts to sense the repeat, and once they sense it, the background stops being atmosphere and becomes a short looping GIF playing behind the text.
Periods that share no common factors take far longer to line up again — long enough that a visitor reads the page and leaves without ever seeing it come round.
The choice costs nothing. It is three numbers in a stylesheet.
Restraint is the actual skill
The hero leans toward the pointer by under two percent. It is slight enough that if you put the two states side by side, most people could not say which one moved.
That is deliberate. The principle we work to is that an effect the visitor consciously notices is usually already too strong. The aim is not for them to see something moving. It is for the surface to feel like it is catching light.
The hard part of this work is not producing the effect — tooling has made that easy. The hard part is turning it down, and down again, until it nearly disappears, and then stopping there.
What carries over to other work
Three questions transfer to any interface, not just a company website.
Who pays for this effect? If the answer is the oldest device among your users, reconsider it.
What does someone see if this fails? If the answer is a blank space, the failure was designed the wrong way round.
Does anything run when nobody is doing anything? If so, there is still something to remove.
If you would like to talk about an interface you are building, get in touch.
About the author
Film — Wisit. A businessman who still does his own BA work more often than he probably should, and writes a fair bit of code, front and back. Runs two or three small businesses. Follows technology and business obsessively, in Thailand and everywhere else. Off the clock: physics, astronomy, DIY, and anything to do with networks. Music always on, though he cannot sing. Plays instruments anyway, badly. Plays a lot of sport, racket sports above all. One flaw: he barely touches video games.
Written with Claude Opus 5