@frapx/react-canvas
react-* — framework binding Wraps @frapx/fx-canvas for React.
A thin lifecycle adapter around @frapx/fx-canvas — it does not replace the
core render loop with React state, and it does not ship any CSS.
pnpm add @frapx/fx-canvas @frapx/react-canvasComponent
Section titled “Component”import { ShaderBackground } from "@frapx/react-canvas";import { glsl } from "@frapx/fx-canvas";
const fragment = glsl` precision highp float; uniform vec2 u_resolution; uniform float u_time;
void main() { vec2 uv = gl_FragCoord.xy / u_resolution; gl_FragColor = vec4(uv, 0.5 + 0.5 * sin(u_time), 1.0); }`;
export function HeroBackground() { return <ShaderBackground fragment={fragment} className="hero-bg" />;}The component renders a wrapper div (position: relative, overflow: hidden
by default) and mounts the shader canvas into it. Most createShaderBackground
options — including feedback — pass straight through as props, and changing
them recreates the underlying instance. They are compared by value (plain
objects and arrays by contents, functions and elements by reference), so an
inline feedback={{ format: "rgba16f" }} keeps the instance while an inline
dpr={() => 1} recreates it; recreateKey forces a new one. uniforms and textures update the
existing instance instead of recreating it, and a new fragment is swapped in
place with setFragment(), so edits and Fast Refresh keep the time and
feedback. A compile error goes to error / onShaderError while the previous
shader keeps drawing.
import { useMemo } from "react";import { useShaderBackground } from "@frapx/react-canvas";
export function ShaderPanel({ intensity }: { intensity: number }) { const uniforms = useMemo(() => ({ intensity }), [intensity]); const { ref, instance, status, error } = useShaderBackground({ fragment, uniforms, feedback: true });
return <div ref={ref} />;}The hook recreates the instance on the same value comparison, so feedback,
vertex and the like need no dependency array. The optional second argument
lists extra values whose change rebuilds the instance. status follows the
instance live ("paused" offscreen or hidden, "context-lost", …) through
onStatusChange, and an onStatusChange option is still called.
Imperative handle
Section titled “Imperative handle”import { useRef } from "react";import { ShaderBackground, type ShaderBackgroundHandle } from "@frapx/react-canvas";
const ref = useRef<ShaderBackgroundHandle>(null);
<ShaderBackground ref={ref} fragment={fragment} />;
ref.current?.stop();ref.current?.setUniform("intensity", 1);Deterministic rendering
Section titled “Deterministic rendering”clock and resolution are props like any other option, but changing them
switches the existing instance in place instead of recreating it. The handle
exposes setTime, setClock, setTimeSource, setResolution, setFragment,
captureFrame and resetFeedback:
<ShaderBackground ref={ref} fragment={fragment} clock="manual" resolution={[1920, 1080]} />;
ref.current?.setTime(2);const png = await ref.current?.captureFrame();timeSource follows an external transport such as an audio player while the
offscreen, hidden and reduced-motion gates keep working (see
Following an audio transport).
The latest function is called on every frame, so an inline arrow function
neither recreates the instance nor restarts u_delta:
<ShaderBackground fragment={fragment} timeSource={() => transport.getPositionMs() / 1000} />Timelines
Section titled “Timelines”uniformSources binds sources such as keyframes() in array order (a later
one wins for the same uniform). Sources are compared by identity, so an inline
array of the same sources does not rebind, and a recreated instance is bound
again. ref.current?.bindUniforms(source) binds one more on the current
instance and returns its removal.
import { keyframes } from "@frapx/fx-canvas";
const timeline = keyframes({ glow: [[0, 0], [2, 1]] });
<ShaderBackground fragment={fragment} uniformSources={[timeline]} />;- React 18+ is supported.
- Safe to import during SSR — WebGL work starts only after mount.
- StrictMode double-mounting is handled by destroying the previous instance in effect cleanup.
- Use
onShaderReady/onShaderErrorinstead of coreonReady/onError. The component also accepts the wrapperdiv’s props, andonErroris thediv’s DOM error event. The hook uses the same names.
Accessibility
Section titled “Accessibility”The wrapper div is aria-hidden="true" by default, since a background effect
is decoration. When the canvas carries meaning, describe it and override the
default:
<ShaderBackground fragment={fragment} role="img" aria-label="Cyan rings pulsing outward on a dark background" aria-hidden={false}/>With the hook, put the same attributes on the element you pass ref to.
Full prop and hook reference: package README on GitHub.