Flow Fields: Gradient Noise + Particles
One of the simplest ideas in generative art produces some of its most hypnotic images: sample a noise function to get an angle at every point in space, then let thousands of particles drift along those angles, leaving a fading trail behind them.
1. What is a flow field?
A flow field (also called a vector field painting) assigns a direction to every point of the canvas. Think of it as an invisible wind map: at coordinate (x, y) there is an angle θ(x, y) that tells a particle passing through which way to turn. Drop enough particles into this wind and let them run, and the aggregate of their paths reveals the hidden structure of the field — swirls, ridges, braided streams.
The technique became popular through the work of generative artists like Tyler Hobbs and reference implementations built on top of Processing/p5.js, but the core algorithm is short enough to write from scratch in plain Canvas 2D in under an hour.
Flow fields look natural because they are built on continuous, spatially-correlated randomness rather than independent per-pixel noise. Nearby points get similar angles, so particles that start close together drift together for a while before the field pulls them apart — exactly the behaviour you see in smoke, wind-blown sand, or a river's current lines.
2. From Perlin noise to a vector field
The angle function θ(x, y) needs to change smoothly across space. Perlin noise is the classic choice: it returns a continuous value in roughly [-1, 1] for any (x, y), with no visible grid artefacts. We turn that scalar into an angle by scaling it to a range of turns:
where: s — spatial noise scale (smaller = larger, smoother swirls), s_t — time scale (how fast the field itself drifts), n — number of full rotations the noise range maps to (n = 1–4 is typical)
Adding the time term t·s_t means the field itself slowly evolves, so a particle that would otherwise settle into a closed loop keeps discovering new territory — this is what keeps a flow-field animation visually alive instead of freezing into a static picture after a few seconds.
Simplex vs classic Perlin
Simplex noise (Perlin's 2001 successor) is slightly cheaper per sample in 2D and has less directional bias than classic Perlin, but for a flow field the visual difference is subtle — classic Perlin, as used on the Perlin Noise page, works perfectly well.
3. Particles: position, velocity, trail
Each particle is a tiny state machine: a position, and implicitly
a short history of where it has been (the trail). Unlike a
physical particle system, we don't usually integrate a full
velocity — the field angle directly sets the direction of travel
each frame, and a constant speed sets how far it
moves:
x += cos(angle) · speed
y += sin(angle) · speed
When a particle drifts off-screen, either wrap it around to the opposite edge (keeps particle count constant, good for dense fields) or respawn it at a random position (better for a "growing" composition that fills in gradually).
3,000–5,000 particles at 60fps is comfortable on Canvas 2D for most laptops. Beyond ~10,000, batch the drawing (see the rendering section below) or move to WebGL points if you need smooth 60fps.
4. Rendering without clearing the canvas
The signature look of a flow field comes from not
fully clearing the canvas each frame. Instead of
ctx.clearRect(...), paint a semi-transparent
rectangle over the whole canvas before drawing the new particle
positions:
ctx.fillRect(0, 0, width, height)
A lower alpha (0.01–0.02) leaves long, ghostly streaks; a higher alpha (0.08–0.15) leaves short comet tails and shows the instantaneous field shape more clearly. This single parameter is the biggest visual lever in the whole sketch.
Each particle is then drawn as a single pixel or a short line
segment from its previous position to its new one — line segments
look smoother at low particle counts, while single pixels with
globalCompositeOperation = "lighter" give the classic
glowing-ember look at high particle counts.
5. Colour: hue from angle or speed
Mapping the particle's colour to the local field angle turns the invisible vector field into a visible one — you can literally see the flow direction encoded as hue:
ctx.strokeStyle = `hsla(${hue}, 80%, 60%, 0.8)`
Alternative mappings worth trying: hue from distance to the centre (radial gradients), hue from elapsed lifetime (particles age from warm to cool), or a fixed curated palette (Ocean, Van Gogh, Fire, Mono) picked by index rather than computed — this is usually what separates a "math demo" look from a "gallery piece" look.
6. Pseudocode
One frame of a flow-field renderer (simplified):
function stepFlowField(particles, t, dt):
// 1. Fade previous frame instead of clearing
ctx.fillStyle = "rgba(10,10,20,0.03)"
ctx.fillRect(0, 0, width, height)
for each particle p:
// 2. Sample the field at the particle's position
angle = noise(p.x * scale, p.y * scale, t * timeScale) * 2 * PI * turns
// 3. Advance along the field
prevX, prevY = p.x, p.y
p.x += cos(angle) * speed
p.y += sin(angle) * speed
// 4. Draw a short trail segment, coloured by angle
hue = (angle / (2 * PI)) * 360
ctx.strokeStyle = hsla(hue, 80, 60, 0.8)
ctx.beginPath()
ctx.moveTo(prevX, prevY)
ctx.lineTo(p.x, p.y)
ctx.stroke()
// 5. Respawn if off-canvas
if outOfBounds(p):
p.x, p.y = randomEdgePosition()
7. Tuning parameters
- Noise scale — small values (0.001–0.003) produce large, lazy swirls; large values (0.01–0.02) produce tight, turbulent knots.
- Time scale — how fast the field itself evolves. Too slow and the picture looks static; too fast and particle paths stop reading as coherent curves.
- Particle count — trades density of coverage against frame rate; more particles fill the canvas faster but cost more per frame.
- Trail alpha — controls streak length, see the rendering section above.
- Speed — very slow particles trace the field precisely but take a long time to build up a composition; fast particles skip over fine structure.
A good starting point: 3,000 particles, noise scale 0.0025, time scale 0.0004, trail alpha 0.04, speed 1.5px/frame. Change one slider at a time on the live simulation below and watch how the composition shifts.
🌊 Try the flow field simulation
Adjust noise scale, time speed, particle count, trail alpha and colour palette live, and save your composition as a PNG.
Open simulation →