Atmospheric web interfaces use shader backgrounds, film grain, frosted glass and glitching display type. When they stutter, it is rarely because one effect is expensive. More often several effects, each reasonable on its own, add up to a full-frame repaint that never stops, and no single code review would have rejected any of them. The argument here is that the rendering budget for such a page has to be stated as per-frame work on a named device instead of as a list of micro-optimisations. The variable that decides most of the cost is whether an effect stays on the compositor or forces paint. The article covers a model of the pipeline, three combinations of effects that reliably fail, rules for when continuous effects should run, and acceptance thresholds tied to interaction metrics.
Composition Failure in Layered Visual Effects
Take a page that gets reviewed one effect at a time. The WebGL terminal background passes, since the shader is trivial and the frame rate holds when it runs alone. The animated film-grain layer is one translucent texture at low opacity, so it passes too. Cards with backdrop-filter: blur() get through because blur is hardware-accelerated on every target browser, and a headline glitch built from animated text-shadow offsets only touches one element. Each of those judgements is correct for the effect it looked at. What none of the reviews checked is what the four do together: in every frame some layer under or over the others changes, so the region that has to be re-rasterised is the whole viewport, all the time, whether the user is doing anything or not.
So the budget has to be counted in work per frame. At a 60 Hz refresh rate the whole pipeline (style, layout, paint, composite, and any application script) gets about 16.7 ms per frame, and the page gets less than that, because the browser needs part of it. A budget stated that way can be checked, which is impossible with an instruction like "keep animations light".
Amdahl's argument about parallel speedup (Amdahl, 1967) carries over to this setting in its general form: the improvement you can get is bounded by the fraction of the work you leave alone. If one layer forces a full-viewport repaint every frame, halving the cost of the other three layers barely moves the frame time, because the repaint is still there. This is the structural reason lists of micro-optimisations disappoint on atmospheric pages. They spread the effort over the part of the frame that was already cheap.
Frame-level timing has mattered for a long time. Nielsen's response-time limits (Nielsen, 1993) put the boundary of feeling instantaneous at roughly 0.1 s and the limit for uninterrupted thought at 1 s, and Card, Robertson and Mackinlay (1991) built the Information Visualizer around similar perceptual timing constraints for animated interfaces. A dropped frame also delays input, since input handled in the same loop waits by the same amount.
The Browser Rendering Pipeline and Two Classes of Animated Property
A browser renders in ordered stages. It recalculates style, lays out geometry, paints pixels into layers, and then composites the layers to the screen. Compositing is comparatively cheap work that often stays on the GPU. Paint and layout are much more expensive. In practice this splits animated properties into two classes, and the class a property falls into decides its cost far more than how the effect looks.
A first-order cost model for one frame:
frame_cost ≈ Σ_layers (paint_area_px × paint_cost_per_px)
+ Σ_layers (composite_area_px × composite_cost_per_px)
+ script_time
with paint_cost_per_px ≫ composite_cost_per_px
The two levers in that model are how many pixels get repainted and how often. Everything else is second order.
| Animated property | Deepest stage forced | Per-frame area at risk |
|---|---|---|
transform, opacity | Composite | Layer only |
filter on a static layer | Composite (usually) | Layer only |
background-position, background-size | Paint | Full painted element |
box-shadow, text-shadow | Paint | Element plus shadow spread |
backdrop-filter | Paint of the backdrop region | Everything behind the element |
width, height, top, left, margin | Layout | Element and its layout dependents |
Use the table for triage. It guarantees nothing, because engines differ, and a property that is compositor-only in one context escalates in another when stacking or clipping forces a layer back into paint.
Three Effect Compositions That Reliably Fail
Animating background-position over a full viewport
If a drifting texture is built as a background-position animation on a full-bleed element, the element repaints every frame. Translating a slightly oversized layer with transform looks exactly the same and stays on the compositor. For a grain layer built the first way, moving the motion to transform removes a viewport-sized repaint from every frame, and the page looks no different.
backdrop-filter layered over continuously changing content
backdrop-filter is cheap when whatever sits behind it is static, because the blurred backdrop can be cached. Put it over a background that animates continuously, such as a shader, a video or a grain layer, and the cache is invalid every frame. The blur is then recomputed for the whole region behind each element that uses it. The cost grows with the number of those elements times their area, and any measurement taken with the background paused misses it completely. Profiling each effect in isolation cannot find this kind of problem, because the cards and the shader are each fast on their own, and the cost only appears when one sits on top of the other.
Long-running text-shadow glitch on large type
Chromatic-aberration glitch effects on display type are usually built from several text-shadow layers with animated offsets. Rasterising text is not cheap, the shadow spread makes the dirty region bigger than the glyph box, and large headline type means a large region. What decides the cost is the duty cycle, more than the cost of any single frame. A glitch that runs almost all the time can usually be cut down to short bursts at intervals. It tends to read as more deliberate that way, and in the budget it turns from a permanent cost into an occasional one.
Residency Rules for Continuously Running Effects
Most of the cost on atmospheric pages comes from effects that keep running when nobody can see them, more than from any one effect being expensive. Four rules cover the common cases.
Run an effect only while it is in the viewport. Decorative effects below the fold should not be animating, and an IntersectionObserver that stops the loop when the element leaves and restarts it when it comes back is enough. A hero shader, for example, identifies the site in the first viewport and is only decoration after that, so the end of the first viewport is the natural place to stop it.
Gate on document visibility. Under some conditions a background tab still services animation callbacks. A visibilitychange handler that pauses every loop takes a few lines and covers the whole site.
Run one loop. When several effects each own a requestAnimationFrame loop, nobody can see the total work per frame. With a single scheduler that ticks registered effects, each at its own interval, the budget becomes one number.
Treat teardown as part of the contract. Every start path needs a stop path that is safe to call twice and safe to call before start. The idempotency discipline that lets data pipelines survive retries is the same thing that keeps a render loop from outliving the component that owns it.
Output Resolution as a Rendering Performance Lever
For procedural backgrounds, output resolution is a parameter you can turn continuously, and almost nobody does. Fragment-shader cost is close to linear in pixel count, and pixel count grows with the square of linear scale. Rendering a canvas at 0.6 of its display size costs about 36% of the full-resolution pixels. On a device with a device pixel ratio of 2, rendering at DPR 1 cuts the work by 4× before the shader changes at all. So render the procedural layer below native resolution and let the browser upscale it.
It is close to free because of how people see. Noise, gradients and blur have no high-frequency detail for anyone to miss, so the upscaling is barely visible, while the same treatment on text or a UI edge is obvious at once. The logic is the same as a tolerance band in perceptual colour tolerancing. The question worth asking is whether a difference is above the threshold at which anyone can detect it, since some difference always exists. Reduce the resolution first and rewrite the shader second, because the first is a one-line change with a quadratic payoff.
Prefers-Reduced-Motion as a Correctness Requirement
WCAG 2.2 treats motion as an accessibility concern, and prefers-reduced-motion is how the platform passes on the user's stated preference. For users with vestibular disorders, whether a page honours it decides whether they can use the page at all, so it cannot be treated as an optional nicety. It is also the cheapest performance win available, because the reduced-motion path is by definition the one without continuous repaints.
The typical bug is specific and easy to ship. A component checks the preference and returns early before rendering its visual output, but the animation loop it already started keeps running. The effect is invisible, the cost is the same as before, and no visual test will catch it. You can recognise it by a requestAnimationFrame loop still executing against an element that is no longer in the DOM. To prevent it, the preference has to gate the loop. Gating only the markup leaves the loop running. The check also has to be a live subscription, since users change the setting mid-session and a one-time read would miss it.
Acceptance Criteria and Audit Procedure
Frame time is the unit you work in, but acceptance should be judged on metrics that describe what a user experiences. Google's Core Web Vitals thresholds give the targets: Largest Contentful Paint at or under 2.5 s, Interaction to Next Paint at or under 200 ms, and Cumulative Layout Shift at or under 0.1, each assessed at the 75th percentile of real visits. Atmospheric effects can break each of the three in a different way. Render-blocking shader initialisation and heavy textures push LCP up. A saturated main thread delays the next paint after input, which shows in INP. CLS rises when an effect layer changes the geometry around it on load.
This procedure finds the real composition costs:
- Pick a target device and a target frame rate, compute the per-frame budget, and subtract browser overhead. Everything after this is measured against that number.
- Profile the page as composed, at rest, with no interaction, and record how much of each frame is paint. A page that spends a substantial share of its frame budget painting while idle has a composition problem, whatever its metric scores say.
- Disable one effect at a time and measure again. If turning off one effect saves more than that effect costs on its own, you have found an interaction between effects. This is where
backdrop-filterover animated content shows up. - Measure with the animated layers running. With the background paused you are measuring a different page from the one users get.
- Check residency directly. Scroll past the effect, switch tabs, turn on reduced motion, and confirm each time that the loop has actually stopped and is not just hidden.
- Confirm against field data at the 75th percentile instead of on the development machine.
Step three is where Knuth's remark on premature optimisation (Knuth, 1974) is useful. The half people quote warns against optimising early. The half that matters here says the critical fraction has to be found by measurement, because intuition about where the time goes is unreliable. The same point applies to process evidence before automation. A model of how a system behaves stays a hypothesis until someone observes the running system and confirms or contradicts it.
Limitations
We have not shown that any particular budget number is right, or that working to a budget gives better results than ad-hoc optimisation. The cost model above is first-order and deliberately ignores differences between engines. Real browsers differ in which properties escalate to paint, when layers get promoted and how backdrop caching behaves, and those details change between versions. The worked figures are arithmetic under stated assumptions, with no measurements behind them, and the failing combinations are described as patterns without a before-and-after measurement series. The claim that downscaled procedural backgrounds look fine is a design judgement. We have not run a study on it. Whether a given atmospheric effect is worth its cost at all is a product question, and the budget supplies only one input to it.
References
- Amdahl, G. M. (1967). Validity of the Single Processor Approach to Achieving Large Scale Computing Capabilities.
- Card, S. K., Robertson, G. G., & Mackinlay, J. D. (1991). The Information Visualizer: An Information Workspace.
- Knuth, D. E. (1974). Structured Programming with go to Statements.
- Nielsen, J. (1993). Usability Engineering.
- World Wide Web Consortium. Web Content Accessibility Guidelines (WCAG) 2.2.
- Google. Web Vitals.