Renderer and effects
A renderer owns one canvas and one context: WebGL2 for GLSL effects, WebGPU for WGSL ones. Effects are definitions it instantiates on demand; any number of them share the same context.
Built-in effects
inkEffect
Cover receding with an organic, fibrous edge.
dissolveEffect
Fractal noise with a glowing edge. Default for details and popover.
wipeEffect
Left to right, noisy edge.
inkVeilEffect
Backdrop spreading like ink. For dialog, viewport layout.
liquidGlassEffect
WebGPU (GLSL twin shown here): a liquid surface with shading, highlights and dispersion. Dialog backdrops, with keepBackdrop.
glassEffect
Light only: a rim with a faint RGB split and a sweep, while CSS frosts the glass.
halftoneEffect
A screen of ink dots sized by noise. For dialog backdrops, with keepBackdrop.
backdropEffect
Backdrop from the center. Default for dialog.
Renderer
import { createRenderer } from 'webgl-ui'
const renderer = createRenderer()
| Member | Description |
|---|---|
canvas | The canvas, aria-hidden, attached only while an animation runs. |
prepare(effect) | Starts initializing an effect (idempotent); resolves true when it can render. Elements call it at creation. |
isReady(effect) | Whether the effect can render now. |
play(host, options) | Runs one animation on host; resolves true when it finishes, false if cancelled. A new play cancels the previous one. |
cancel(owner?) | Stops the active animation (only if it belongs to owner, when given). |
dispose() | Frees the context and every effect. |
A lost WebGL context re-initializes every effect when it comes back; in between, elements are native.
Your own GLSL effect
A fragment shader in GLSL ES 3.00. It may declare any of these uniforms and must write premultiplied alpha. See the cookbook recipe for a complete one.
| Uniform | Type | Value |
|---|---|---|
uProgress | float | 0 → 1 while showing, 1 → 0 while hiding (eased). |
uTime | float | Seconds, continuous across reversals. |
uResolution | vec2 | Canvas size in physical pixels. |
uColor | vec3 | Cover or backdrop color, sRGB 0..1. |
uAlpha | float | Backdrop alpha (dialog); 1 elsewhere. |
uPointer | vec2 | Normalized pointer position (reserved). |
import { defineGlslEffect } from 'webgl-ui'
const fade = defineGlslEffect('fade', `#version 300 es
precision highp float;
uniform float uProgress;
uniform vec3 uColor;
out vec4 outColor;
void main() {
float a = 1.0 - uProgress;
outColor = vec4(uColor * a, a);
}`)
A dialog backdrop effect must end, at uProgress = 1, on a uniform uColor with alpha uAlpha: that is what CSS shows after the hand-over.
Rules for effect authors
- Write premultiplied alpha. A cover effect is fully transparent at
uProgress = 1; a backdrop effect ends on a uniformuColor×uAlpha, unless it is used withkeepBackdrop, which hands its last frame to CSS as an image (halftoneEffect). - No periodic change in luminance faster than three times per second (WCAG 2.3.1). One sweep per interaction is the model.
- Durations are capped at five seconds (
MAX_DURATION, WCAG 2.2.2). - Never carry meaning in the effect alone: the native element must say everything.
- Effects step aside entirely under reduced motion, forced colors, more contrast and reduced transparency (
effectsAllowed()); nothing to do in the shader.
WGSL effects (WebGPU)
The core also draws with WebGPU, with no dependency. A WGSL effect is one function returning premultiplied color; the standard inputs are in the uniform fx. Give it a GLSL twin as fallback: where WebGPU is missing (no navigator.gpu, no adapter) the fallback runs on WebGL2 instead, and with neither the elements are native.
import { createRenderer, createDetailsFx, defineWgslEffect, inkEffect, inkGpuEffect } from 'webgl-ui'
const fade = defineWgslEffect('fade', `
fn effect(uv: vec2f, frag: vec2f) -> vec4f { // uv: 0..1, origin top left
let a = 1.0 - smoothstep(fx.progress - 0.1, fx.progress + 0.1, 1.0 - uv.y);
return vec4f(fx.color.xyz * a, a);
}`, { fallback: inkEffect })
// A canvas holds one kind of context: WGSL effects get a renderer of their own.
const gpuRenderer = createRenderer()
createDetailsFx(details, { renderer: gpuRenderer, effect: inkGpuEffect }) // the ink, in WGSL
| Input | Value |
|---|---|
fx.progress, fx.time | As uProgress, uTime. |
fx.resolution, fx.pointer | As uResolution, uPointer. |
fx.color.xyz, fx.alpha | As uColor, uAlpha. |
uv, frag | 0..1 with the origin at the top left (the WebGPU convention); fragment position in pixels. |
Built in: inkGpuEffect and inkVeilGpuEffect, the ink and the veil in WGSL with their GLSL versions as fallbacks. Image sampling and params are GLSL-only for now. Live: WebGPU playground page.
TSL effects (three.js)
The optional package packages/tsl builds effects as TSL node graphs; three.js is loaded lazily and is never a core dependency. A canvas holds one kind of context, so TSL effects need a renderer of their own.
import { createRenderer, createDetailsFx } from 'webgl-ui'
import { defineTslEffect } from 'webgl-ui-tsl'
const fade = defineTslEffect('fade', ({ progress, color, uv }, { vec4, smoothstep }) => {
const a = smoothstep(progress.sub(0.1), progress.add(0.1), uv.y).oneMinus()
return vec4(color.mul(a), a) // premultiplied; uv origin at the top left
})
const tslRenderer = createRenderer()
createDetailsFx(details, { renderer: tslRenderer, effect: fade })
Live: TSL playground page.