Frameline’s production bar includes documented cost per material — approximate bundle impact, rendering technique, and a note from mid-tier device testing. Treat those numbers as release criteria, not marketing fluff.
Perf budgets
| Tier | Technique | Target |
|---|---|---|
| CSS-only | Gradients, filters, noise via CSS | Negligible JS; fine for many instances |
| Canvas / light WebGL | Single full-bleed hero or section | One active instance in view; pause offscreen |
| Heavy WebGL | Multi-pass or high-res shaders | Hero-only; prefer static export elsewhere |
- Read the specs block on each material page before committing to a technique in a dense dashboard layout.
- Prefer one live material per viewport. Stacking multiple WebGL canvases is the fastest way to miss frame budgets.
- Measure on a mid-tier laptop or phone — not only on a workstation GPU.
Intersection-activated previews
Catalog and collection grids use intersection-activated previews: WebGL / canvas only mounts while a card is near the viewport. Off-screen tiles stay on the static CSS shell, so scrolling a dense grid does not mean forty live shaders. Prefer the same pattern in your own lists — activate on enter, freeze on leave.
On detail pages and single heroes, also pause when the material leaves the viewport or the document is hidden:
useEffect(() => {
const el = ref.current
if (!el) return
const io = new IntersectionObserver(
([entry]) => setActive(entry.isIntersecting),
{ rootMargin: "100px" },
)
io.observe(el)
const onVis = () => setActive(document.visibilityState === "visible")
document.addEventListener("visibilitychange", onVis)
return () => {
io.disconnect()
document.removeEventListener("visibilitychange", onVis)
}
}, [])Shipped materials that animate should already honor this contract. If you fork the source, keep the observer — silent GPU work in a scrolled-away footer is a common regression.
CSS-only tier (8 materials)
Eight catalog materials are CSS-only — no WebGL, negligible JS — and publish honest “negligible GPU” perf notes:
Use CSS-only materials (or forced static mode on WebGL materials) when:
- Email, PDF, or static export contexts.
- Low-power devices or data-saver preferences.
- Dense UI where many surfaces appear (cards in a grid, table empty states).
- Environments where WebGL is blocked or flaky (locked-down enterprise browsers).
<GrainField forceStatic className="absolute inset-0" aria-hidden />
Pair with reduced-motion fallbacks — both paths should land on the same static composition when motion is off or WebGL fails.