Effects
Lazy, reduced-motion-aware CanvasUI WebGL effects for public hero surfaces — with a live playground and per-control docs.
Each effect is code-split into its own chunk and only fetched when it activates on the client. Under prefers-reduced-motion, without WebGL2, or (for heavy effects) on small screens, no animation runs — a static token gradient shows instead. The shader renders over WebGL2 everywhere; where the experimental html-in-canvas API exists it additionally refracts the real DOM while text stays selectable. Never wire these into data-dense or authed screens.
Effects vendored from CanvasUI © David Haz, used under its MIT + Commons Clause license as part of our own applications. Vendored and used as part of our own applications and websites. We do not offer these components for redistribution. This gallery is an internal design-system reference.
Liquid — aurora background
A GPU fluid that drifts behind hero copy. Lazy-loaded, reduced-motion-aware, and degrades to a static token gradient. This is the effect running on the reference-app hero.
BadgeButtonLabelSelectSelectContentSelectItemSelectTriggerSelectValueSeparator
Text stays crisp and selectable
It drifts on its own the moment it loads — move your pointer across the surface to take over, and drag the controls to retune it live.
Strength of the colour tint the flow leaves behind.
How strongly the flow warps what is underneath.
Rotational force fed back into the flow — more swirl.
How long the dye trail persists (closer to 1 lasts longer).
Size of each injected splat of colour.
Multi-colour: the trail is drawn from the active THEME palette instead of one fixed hue. Flow direction picks the position on the ramp, so the motion still chooses the colour — but every colour is one of the theme’s.
How much of the palette one flow sweep traverses. Low is a subtle two-tone drift; high cycles the whole palette more often. Only applies with Rainbow on.
Which self-running motion drives the field at rest — aurora band, rising smoke/vapor, a directional drift, or a slow swirl.
How fast wisps/samples are emitted — denser field as it rises.
Size of each puff and how wide the field spreads out.
Speed of the ambient flow — how briskly the field moves.
How long each wisp lives before it fades and a new one appears.
Swirl and chaos in the drift — higher reads as billowing smoke.
Liquid is a real-time fluid simulation running entirely on the GPU in WebGL2. A velocity field is advected each frame, pressure is solved over a few Jacobi iterations, and a dye texture is pushed around by the flow — the same maths behind film smoke, distilled to a few fragment-shader passes.
As a hero background it runs content-less: the dye colours drift over your token gradient while the wrapper injects a gentle self-running current so it stays alive even when no one is touching it. Move the pointer across it and you add real velocity — the flow responds under your cursor.
That self-running motion is a pluggable ambient algorithm: pick "Ambient" to switch between the cohesive aurora band (the hero default), rising smoke/vapor wisps that come into existence, spread, and dissipate, a calm directional drift, or a slow swirl — and retune its rate, size, drift, life, and turbulence live. The colour tracks your theme.
Where the experimental html-in-canvas API is available (Chrome origin trial) the same shader can additionally refract the real DOM beneath it while the text stays selectable; everywhere else it degrades to the pure WebGL2 background. Under prefers-reduced-motion, or without WebGL2, only the static gradient shows.
| Control | Default | What it does |
|---|---|---|
intensity | 2 | Strength of the colour tint the flow leaves behind. |
distortion | 0.4 | How strongly the flow warps what is underneath. |
curl | 1.9 | Rotational force fed back into the flow — more swirl. |
densityDissipation | 0.96 | How long the dye trail persists (closer to 1 lasts longer). |
radius | 0.3 | Size of each injected splat of colour. |
rainbow | false | Multi-colour: the trail is drawn from the active THEME palette instead of one fixed hue. Flow direction picks the position on the ramp, so the motion still chooses the colour — but every colour is one of the theme’s. |
rainbowSpread | 1 | How much of the palette one flow sweep traverses. Low is a subtle two-tone drift; high cycles the whole palette more often. Only applies with Rainbow on. |
ambient | aurora | Which self-running motion drives the field at rest — aurora band, rising smoke/vapor, a directional drift, or a slow swirl. |
ambientRate | 1 | How fast wisps/samples are emitted — denser field as it rises. |
ambientSpread | 1 | Size of each puff and how wide the field spreads out. |
ambientDrift | 1 | Speed of the ambient flow — how briskly the field moves. |
ambientLife | 1 | How long each wisp lives before it fades and a new one appears. |
ambientTurbulence | 1 | Swirl and chaos in the drift — higher reads as billowing smoke. |
Ripple — interactive water
Refractive rings that spawn on hover/click and drift ambiently. Light enough for phones; distorts real DOM where html-in-canvas is available.
BadgeButtonLabelSelectSelectContentSelectItemSelectTriggerSelectValueSeparator
Text stays crisp and selectable
It drifts on its own the moment it loads — move your pointer across the surface to take over, and drag the controls to retune it live.
What spawns ripples: click, hover (leaves a wake), or none (ambient only).
Seconds between self-spawned ambient ripples. 0 disables them.
Height of the waves.
Distance between wave crests, in pixels.
Number of crests in each wave train.
How far the waves bend the content beneath, in pixels.
Chromatic split of colours along the wave slopes.
Intensity of the light glints on the crests.
Ripple renders expanding wave trains as an analytic height field in a single fragment-shader pass, then refracts the surface along the wave slopes with a touch of chromatic dispersion and specular glint on the crests — so it reads like light through moving water.
It is interactive by nature: rings spawn on pointer press, and in hover mode a wake follows the cursor. An ambient interval also drops rings at random positions on its own, so the demo stays alive with no input. It is light enough to run on phones.
Like the other effects it runs over WebGL2 everywhere and enhances to distort real DOM where html-in-canvas is available. Reduced-motion and no-WebGL2 viewers get the static base layer.
| Control | Default | What it does |
|---|---|---|
trigger | hover | What spawns ripples: click, hover (leaves a wake), or none (ambient only). |
interval | 2 | Seconds between self-spawned ambient ripples. 0 disables them. |
amplitude | 0.9 | Height of the waves. |
wavelength | 80 | Distance between wave crests, in pixels. |
rings | 3 | Number of crests in each wave train. |
refraction | 100 | How far the waves bend the content beneath, in pixels. |
dispersion | 0.5 | Chromatic split of colours along the wave slopes. |
shine | 1.1 | Intensity of the light glints on the crests. |
Glass — refractive lens
A physically-motivated glass lens that follows the pointer, with chromatic aberration and fresnel rim. A single-pass shader, cheap on mobile.
BadgeButtonLabelSelectSelectContentSelectItemSelectTriggerSelectValueSeparator
Text stays crisp and selectable
It drifts on its own the moment it loads — move your pointer across the surface to take over, and drag the controls to retune it live.
Lens outline.
Lens radius (or half-height), in pixels.
Index of refraction — higher bends light more.
How sharply the rim curves away.
Chromatic fringing strength at the rim. 0 disables it.
Fresnel reflection strength on the rim.
Frosted blur of the glass face. 0 is optically clear.
Glass is a physically-motivated lens: it traces refraction through a bevelled glass surface with a configurable index of refraction, adds chromatic aberration at the rim, a fresnel reflection, and an optional specular shine that keeps the lens visible even over flat backgrounds.
On load it drifts gently on its own as a floating lens, so it is visibly alive before you touch it; move the pointer and it follows your cursor, and it can magnify elements you mark as targets. It is a single-pass shader — cheap enough for phones. With html-in-canvas it refracts the live DOM; without it, it renders as a self-lit glass bead over the base layer.
Reduced-motion viewers and browsers without WebGL2 see the static base layer, never a broken canvas.
| Control | Default | What it does |
|---|---|---|
shape | circle | Lens outline. |
size | 120 | Lens radius (or half-height), in pixels. |
ior | 1.5 | Index of refraction — higher bends light more. |
bevel | 4 | How sharply the rim curves away. |
aberration | 1 | Chromatic fringing strength at the rim. 0 disables it. |
reflection | 1 | Fresnel reflection strength on the rim. |
blur | 0 | Frosted blur of the glass face. 0 is optically clear. |
Particle Object — 3D particle cloud (three.js)
The one three.js effect: a 3D form dissolved into a cursor-reactive cloud of thousands of drifting particles. The entire three engine is isolated to this effect’s own lazy chunk — absent from the base bundle and every other effect chunk. Runs self-contained from a built-in procedural form, no external asset.
BadgeButtonLabelSelectSelectContentSelectItemSelectTriggerSelectValueSeparator
Text stays crisp and selectable
It drifts on its own the moment it loads — move your pointer across the surface to take over, and drag the controls to retune it live.
Which shape the particles rebuild. The vector forms are sampled from live paths and text in an offscreen canvas — no model, font, or decoder is fetched — and are inflated to a rounded volume rather than a flat cutout. "Custom" renders whatever silhouette the host app supplies.
How many particles the form is rebuilt from.
Size of each particle, in pixels.
Radius of the cursor's push field, in pixels.
How hard the cursor shoves particles away.
Tangential curl of the push — particles spiral rather than only flee.
How quickly displaced particles return home.
Strength of the idle floating bob.
Spin the form slowly on its own, turntable-style.
Particle Object is the outlier here: a real-time three.js scene, not a fragment-shader pass. A 3D form is rebuilt from thousands of GPU particles that hold its silhouette, float and rock gently on their own, and shimmer in place — a genuine WebGL 3D object rendered with the three library.
It is the reason `three` is a dependency at all, and it is deliberately isolated: the three engine and this scene live entirely inside this effect’s own dynamically-imported chunk, so they never enter the base bundle or any of the fragment-shader effect chunks. Nothing is fetched to run it — no model, no font, no decoder — so it works offline and inside a strict CSP.
The form is a control, not a constant. Two forms are procedural geometry (a torus knot, a sphere shell); the others are sampled from vectors at runtime — a wordmark shaped from whatever font the page already has, and whatever silhouette the host app hands in as data. Both are rasterised to an offscreen alpha mask, and a distance transform inflates that silhouette into a rounded volume, so a 2D source still reads as a 3D object from any angle. The library ships no artwork of its own: an app passes its own mark in through `formSpec`, so adding the next form is data, not code.
It is cursor-reactive: move the pointer across the surface and a push field shoves the nearest particles aside, and they spring back home. Under prefers-reduced-motion, or without WebGL, only the static token gradient shows, and it simplifies out on small screens for frame-rate.
| Control | Default | What it does |
|---|---|---|
form | torusKnot | Which shape the particles rebuild. The vector forms are sampled from live paths and text in an offscreen canvas — no model, font, or decoder is fetched — and are inflated to a rounded volume rather than a flat cutout. "Custom" renders whatever silhouette the host app supplies. |
count | 18000 | How many particles the form is rebuilt from. |
size | 2 | Size of each particle, in pixels. |
radius | 140 | Radius of the cursor's push field, in pixels. |
strength | 2.4 | How hard the cursor shoves particles away. |
swirl | 0.6 | Tangential curl of the push — particles spiral rather than only flee. |
spring | 4 | How quickly displaced particles return home. |
floatIntensity | 1 | Strength of the idle floating bob. |
autoRotate | true | Spin the form slowly on its own, turntable-style. |