Taming WebGL: a Three.js hero that doesn't tank your LCP
A particle field is gorgeous and a great way to ruin Core Web Vitals. The exact recipe behind this site’s hero.
Why the pretty hero hurts
A full-screen Three.js scene is heavy in three ways at once: the script parse, the WebGL context creation, and a render loop that never stops. Mount it eagerly and it competes with your largest contentful paint for the main thread at the worst possible moment.
Lazy-init after first paint
- Render the text hero with plain CSS first so LCP fires on real content.
- Spin up the WebGL context after
requestIdleCallback(or a short timeout fallback), well after first paint. - Fade the canvas in — the page never looks broken while it boots.
Cap the pixel ratio
Retina screens will happily ask for a 3× framebuffer. Clamp devicePixelRatio to ~2 and the fragment shader does a fraction of the work for a difference almost no one can see.
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));Stop rendering when no one's looking
An IntersectionObserver pauses the animation loop when the hero scrolls off-screen, and a visibilitychange listener kills it when the tab is backgrounded. Idle GPU, cool fans, happy battery.
That’s the whole recipe behind the hero on this very site — gorgeous on arrival, invisible in the performance trace.