Generative Flow Field
200 particles following a field of directions drawn from a smoothed noise that slowly drifts: the canvas is never cleared, only veiled by 5% per frame, so the particles leave long silky threads that trace the current's lines — a 2D canvas sharp on Retina, threads already drawn on first display, a loop that stops off screen, a still image when the user reduces motion. A generative algorithm, no artificial intelligence.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Liquid has 31 effects, including 6 free. Explore the category →
3 usage examples



How it works
The .fx-0412-root frame is 300 px tall and full width; the canvas fits it (display: block, 100% × 100%), never the other way around. Its internal size is the displayed size multiplied by devicePixelRatio and drawing happens in CSS pixels (setTransform): measured at density 2, 1280 × 600 for 640 × 300 displayed. A ResizeObserver follows the frame; the height stays at 300 px after 20 resizes in a row (measured in Chromium 151, Firefox 153 and WebKit 26).
The field comes from value noise: a pseudo-random value is drawn at each node of a grid (fract(sin(12.9898 x + 78.233 y) × 43758.5453)), then smoothed between nodes with an S-curve. A particle reads this noise at its position × 0.005 (one cell every 200 px) and multiplies it by 4π: it takes that direction and moves 0.5 to 2 px per frame depending on its own speed. Time is added to the noise's vertical coordinate (+0.003 per frame): the field slides and slowly rearranges, never repeating exactly. A particle that leaves the frame is reborn somewhere at random; each keeps its hue, between 240 and 300 (indigo to magenta), as 1.5 px dots.
The threads come from the veil: on each frame, an rgba(10, 10, 15, 0.05) rectangle covers the whole canvas, so a dot fades in about sixty frames. The veil's color is the background's, otherwise the frame slowly turns that color. Resizing a canvas clears it: the background is repainted, particles outside the new frame are moved back in, and on first display 40 steps are replayed at once so the threads are already there. The requestAnimationFrame loop stops when an IntersectionObserver sees the canvas leave the screen (measured: 0 frames per second off screen) and resumes when it comes back.
Accessibility
- Reduced motion: under
prefers-reduced-motion: reduce, the script computes 120 steps without showing them and displays the resulting image, frozen threads; no loop. Measured in all three engines: 0% of pixels changed in 500 ms, threads visible over the whole surface. - Decorative role: the canvas carries no information. Put your text on top over a fairly opaque background (a dark gradient as in the examples): the light threads go everywhere and can cut through a thin line.
- Background motion: 20% of pixels change in 500 ms, continuously. On a long reading page, keep the effect to a banner or slow the particles down (see Customize).
- Speed tied to the screen: everything is counted per frame; on a 120 Hz screen, particles move twice as fast as at 60 Hz.
Browser compatibility
2D canvas + 2.7 KB of JavaScript, no dependency. Measured in Chromium 151, Firefox 153 and WebKit 26: same field, same threads, internal size doubled at density 2, stable height, loop stopped off screen, still image under reduced motion. Measured cost per frame: 0.2 ms of JavaScript in Chromium, 2 to 3 ms in Firefox (without graphics acceleration); the 40 first-display steps take about 10 ms in Chromium and up to 100 ms in Firefox, once. ResizeObserver exists since Chrome 64, Firefox 69 and Safari 13.1.
Without JavaScript, the frame stays bluish black (#0a0a0f), with no threads. Without IntersectionObserver, the loop also runs off screen; without ResizeObserver, the canvas follows window resizing only.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0412-root">
<div class="ai-flow-field-container">
<canvas class="ai-flow-canvas" width="393" height="280"></canvas>
</div>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Particle count (JS — length: 200) |
200 | 400 gives a denser weave; the cost per frame grows accordingly. Under 80, the threads become sparse. |
Thread length (JS — rgba(10, 10, 15, 0.05)) |
5% veil per frame | 0.02 lengthens the threads (the frame fills up), 0.15 gives short comets. Keep the same color as the .fx-0412-root background and the #0a0a0f repainted on resize. |
Hues (JS — hue: 240 + 60 * Math.random()) |
240 to 300 (indigo to magenta) | 185 + 35 * Math.random() gives cyan to blue; 30 + 20 * Math.random() golden threads. Saturation and lightness in hsla(…, 80%, 65%, 0.8). |
Speed (JS — speed: .5 + 1.5 * Math.random()) |
0.5 to 2 px per frame | Halve it for a lazy current, ideal behind text. |
Swirl scale (JS — .005 * n.x, .005 * n.y) |
0.005: one noise cell every 200 px | 0.01 gives swirls twice as tight, 0.0025 broad, nearly straight sheets. |
Field drift (JS — i += .003) |
0.003 per frame | 0 freezes the field (threads end up tracing fixed lines), 0.01 makes it change within a few seconds. |
Height (CSS — .fx-0412-root { height: 300px }) |
300 px | The canvas follows; on first display, the threads are computed for the new size. |
FAQ
h with a noise library: the rest of the code does not change.