data-vfx-chain gives an element a chain of per-pixel WebGL2 kernels — warps,
displacement maps, generated noise — that the runtime repaints from (t, params)
every time the composition seeks. It mirrors the shipped audio effects rack
(data-fx-chain): a versioned JSON chain attribute, static params in JSON,
animated params as CSS custom properties GSAP tweens like any other exporter
variable.
Use it for a per-pixel treatment a CSS filter or SVG <filter> cannot express —
a wave warp, a self-referential displacement map, or a generated fractal-noise
texture — where you need the same frame twice at the same t to produce
byte-identical pixels.
Element
data-vfx-chainis JSON with the same field names asdata-fx-chain:version,nodes[], and each node’stype/id/enabled/params.- Static params live in
params. Animated params live in CSS custom properties named--vfx-<nodeId>-<key>on the host element, tweened by GSAP like every other exporter variable. The runtime reads the CSS var first on every paint and falls back toparamswhen it doesn’t resolve. .hf-vfx-src(a<canvas layoutsubtree>) is present only when some node’s def needs to read pixels besides(x, y, t, params)— see Capture below. Its one child,.hf-vfx-in, is the texture source. Give.hf-vfx-inan explicit pixel box (width/height, the layer’s own size) —position:absolute; inset:0has no containing block to resolve against inside alayoutsubtreecanvas and collapses to 0×0, which makes the capture succeed and draw nothing, silently.- Effects that run before the chain in After Effects order belong on
.hf-vfx-in(inside the capture); effects that run after belong on the host (outside, applied to.hf-vfx-outby the ordinary page compositor). Chain order is nesting order. .hf-vfx-outis created by the runtime if you don’t provide it. It isposition:absolute; inset:0, sized to the host’s own (untransformed) layout box —offsetWidth/offsetHeight— times devicePixelRatio, not the host’s transformed bounding box. A GSAP scale or rotation on the host does not inflate or stretch the capture.
Capture
Every kernel declares acapture requirement on its def, not the element:
A
self or backdrop node puts the whole composition on screenshot capture
pinned to one worker (the htmlInCanvas render-mode hint exists because a
paint-cache race across parallel browser instances is a measured failure, not
a theoretical one). A none node still forces screenshot capture — WebGL
content is not something the fast drawElementImage path can read back
faithfully — but leaves worker count auto-resolved.
Neither is a regression against the alternative of baking the effect in After
Effects and shipping video instead of a live chain: a bake costs a full AE
render up front and produces a filmstrip nothing can retime. But compared to a
drawElement render of the same composition without the node, both self
and none capture are slower. Budget for it, and don’t add a chain to a layer
that doesn’t need per-pixel treatment.
Determinism
Same backend, same(u_t, params) → byte-identical .hf-vfx-out pixels. A
kernel may read only (u_size, u_t, u_fps, params, u_src) — no Math.random,
no clock, no state carried between paints. That’s what makes data-vfx-chain
safe to seek anywhere in a render, including backwards, and still get the same
frame you’d get scrubbing forward to it in Studio.
Studio and browser support
self and backdrop capture depend on drawElementImage, the same
experimental Chromium API HTML in Canvas uses. In a
normal browser preview, enable chrome://flags/#canvas-draw-element in a
compatible Chrome or Brave build and restart it before opening a composition
that uses a capturing chain. Without it — or on a browser build with no
WebGL2 — a chain fails loudly (a [HyperFrames] composition script error:
console line naming the flag) rather than silently rendering the wrong thing.
none-capture chains (like fractal-noise) need only WebGL2, not the flag.
Known constraints
A few things the runtime implementation found worth knowing before you author a chain by hand:.hf-vfx-inneeds an explicit pixel box. See Element above — this is the one that costs a silent, empty capture if you skip it.- Only immediate children of the
layoutsubtreecanvas can be captured.drawElementImagethrows on a grandchild..hf-vfx-inmust be the canvas’s only child. - Output size follows the host’s layout box, not its transformed bounding
box. A GSAP
transform: scale()or rotation on the host does not affect.hf-vfx-out’s resolution. - A chain repaints on seek, not on every animation-frame tick. Every render path seeks per frame, so this only matters for Studio scrubbing-free playback, where a chain does not repaint between seeks.
- Known conflict with page-side shader transitions. A composition running
both
@hyperframes/shader-transitions’ page-side compositing and a capturingdata-vfx-chainnode can have the chain painted from a stale texture outside a transition’s own active window. If your composition uses both features together and a chain looks wrong outside a transition, this is why.
Related topics
HTML in Canvas
The same
drawElementImage capture, for compositing DOM into a 3D scene
instead of a per-pixel kernel.Audio effects implementation
The
data-fx-chain shape data-vfx-chain mirrors, for the audio rack.