One canvas.
Every scene.
Anchor independent WebGL programs to any DOM element. A single fixed canvas, scissor-tested viewports, one shared render loop. ScrollRenderer is a WebGL scene manager for inline WebGL effects that scale.
No iframes. No per-element canvases. Just one shared GL context.
Each ScrollScene tracks a DOM element via
getBoundingClientRect() every frame. WebGL's scissor test
clips each render to that element's exact pixel bounds. Scenes outside
the viewport are culled automatically by IntersectionObserver.
DOM anchored
Every scene knows its element. u_origin and
u_resolution update each frame automatically, with no
event wiring required.
Scissor-tested viewports
The rings above are centred on this card's exact position in the
canvas using u_origin.xy and
gl_FragCoord, demonstrating element-relative fragment
math.
Single render loop
One requestAnimationFrame drives all scenes. The
shared GL context means zero per-canvas overhead, no
synchronisation, and no scheduling jank between effects.
Multiple WebGL programs don't scale.
The traditional answer to "WebGL effects on multiple page elements"
is one
<canvas> per effect - a separate GPU context for
each. Browsers cap active WebGL contexts. Each one competes for GPU
memory, initialises its own GL state, and drives its own render loop
with zero coordination between them.
In addition to CPU, GPU and memory load, you also run into issues with browswers dropping contexts in order to free up resources, causing your effects to disappear without warning. This requires you to create complex context-management solutions that are fragile and error-prone.
ScrollRenderer is designed to solve these problems with a single shared context and one coordinated render loop.
Your markup and your GL, in the same stack.
A single fixed <canvas> sits on top of your page.
ScrollRenderer scissor-clips each scene to its DOM
element, so WebGL effects land exactly where your HTML does
Scroll to see how the layers relate: the HTML document lays the page out, including the elements that serve as placeholders for the WebGL programs which are composed into the shared, full-screen canvas that renders everything in place via a scroll-renderer coordinator..
Three lines to anchor a shader to any element.
const renderer = new ScrollRenderer() const scene = new ScrollScene({ element, scene: drawable }) renderer.addScene(scene) // Uniforms updated automatically every frame: // u_time float - elapsed time // u_resolution vec2 - element size in physical pixels // u_origin vec4 - .xy pixel origin (gl_FragCoord space) // .zw element centre in canvas NDC
Extend with onBeforeRender and
onAfterRender hooks, or opt out of per-scene clearing
and viewport locking independently.
GPU simulations alongside fragment shaders.
The TransformFeedback class handles ping-pong buffers
automatically. Complex particle systems run in the shared context
alongside simple fragment-shader scenes with no extra overhead.
- Curl-noise velocity field, GPU-side
- Per-particle life, respawn, and seed
-
Composites over adjacent scenes via
clearOnRender: false -
Positioned with
u_origin.zwwhenuseViewportis false