Flow field art: how a noise flow field works, with code
Thousands of lines that stream, curl and gather like hair, wind or ink in water. How a flow field works, a canvas starter with its own noise that you can paste and run, and the skill that draws one in sumi ink as a website hero.
Free skills to start. Pro is $79 a year for every skill, or $9.99 a month for 10. See pricing
What is a flow field?
A flow field is a map of directions laid over the canvas: every point has an angle. Drop particles in and move each one a little along the angle where it stands, and their paths become smooth, flowing lines. Draw the paths and you get the look generative artists love: combed hair, wind maps, wood grain, marbled ink.
The angles usually come from noise, most often Perlin noise, because it changes smoothly from point to point: neighboring particles move almost alike, so the lines stay coherent instead of scribbling. Flow fields are one of the best-known techniques in generative art, helped along by Tyler Hobbs’s essay on them and his Fidenza series.
On a website a flow field makes a calm, organic hero that draws itself while people read. It suits studios, print and paper brands, weather, audio and anything that should feel crafted rather than slick.
How the effect works
Read noise at each point and turn it into an angle: angle = noise(x × scale, y × scale) × 2π × turns. A small scale gives broad sweeping currents and a large one tight turbulent detail. More than one turn lets the field curl back on itself. Feed time in as a third noise input and the whole field drifts.
Each frame, every particle reads the angle where it stands, takes a short step that way and draws a line from its old position to the new one. Give particles a lifetime and send them to a random spot when they expire or leave the canvas, so the coverage stays even. Keep the strokes and they build into a drawing; fade the canvas a little each frame and you get moving trails instead.
01
Make smooth noise
Value noise blends random numbers on a grid; Perlin noise blends random gradients and looks smoother. Either works as long as it is continuous.
02
Turn noise into angles
Multiply the noise by 2π and by one to three turns. Add a finer octave at a quarter of the weight for detail.
03
Move the particles
A step of a pixel or two per frame along the angle, with a short line drawn for each step.
04
Respawn and repeat
Restart particles that age out or leave the edges, and start a fresh sheet with new noise when the drawing is full.
HTML
A noise flow field on a 2D canvas
Fifteen hundred particles follow a field made from its own small value noise and pile their strokes into a drawing; then the sheet fades and a new field starts. Raise TURNS for tighter curls and SCALE for finer detail. Reduced motion gets one finished sheet.
<canvas id="flow" style="position:fixed;inset:0;width:100%;height:100%;background:#f3efe6"></canvas>
<script>
const canvas = document.getElementById('flow')
const g = canvas.getContext('2d')
const N = 1500, SCALE = 0.0022, SPEED = 1.2, TURNS = 2 // TURNS: how far the noise can twist the flow
const SHEET = 480, FADE = 40 // frames: draw a sheet, then fade it out and start a new one
const PAPER = '#f3efe6', INK = 'rgba(22,24,32,0.2)', ACCENT = 'rgba(214,72,46,0.5)'
const still = matchMedia('(prefers-reduced-motion: reduce)').matches
// Smooth value noise: random values on a 3D lattice, blended with a smoothstep.
// The third axis is time, so the field drifts without jumping.
const hash = (x, y, z) => {
let h = (x * 374761393 + y * 668265263 + z * 2147483647) | 0
h = Math.imul(h ^ (h >>> 13), 1274126177)
return ((h ^ (h >>> 16)) >>> 0) / 4294967295
}
const smooth = (t) => t * t * (3 - 2 * t)
const lerp = (a, b, t) => a + (b - a) * t
function noise(x, y, z) {
const X = Math.floor(x), Y = Math.floor(y), Z = Math.floor(z)
const u = smooth(x - X), v = smooth(y - Y), w = smooth(z - Z)
const c = (i, j, k) => hash(X + i, Y + j, Z + k)
return lerp(
lerp(lerp(c(0, 0, 0), c(1, 0, 0), u), lerp(c(0, 1, 0), c(1, 1, 0), u), v),
lerp(lerp(c(0, 0, 1), c(1, 0, 1), u), lerp(c(0, 1, 1), c(1, 1, 1), u), v), w)
}
// The field: every point on the canvas has an angle
const angle = (x, y, t) => (noise(x * SCALE, y * SCALE, t) * 0.75 + noise(x * SCALE * 3, y * SCALE * 3, t + 9) * 0.25) * Math.PI * 2 * TURNS
let w, h, dpr
function size() {
dpr = Math.min(devicePixelRatio, 2)
w = canvas.width = innerWidth * dpr
h = canvas.height = innerHeight * dpr
g.fillStyle = PAPER; g.fillRect(0, 0, w, h)
}
size()
const spawn = (p) => { p.x = Math.random() * w; p.y = Math.random() * h; p.life = 60 + Math.random() * 180; return p }
const parts = Array.from({ length: N }, (_, i) => spawn({ accent: i % 40 === 0 }))
let t = Math.random() * 100
function step() {
g.lineWidth = dpr
for (const color of [INK, ACCENT]) { // one path per color: far fewer draw calls than one per particle
g.strokeStyle = color; g.beginPath()
for (const p of parts) {
if ((color === ACCENT) !== p.accent) continue
const a = angle(p.x / dpr, p.y / dpr, t) // read the field where the particle stands
const nx = p.x + Math.cos(a) * SPEED * dpr, ny = p.y + Math.sin(a) * SPEED * dpr
g.moveTo(p.x, p.y); g.lineTo(nx, ny)
p.x = nx; p.y = ny
if (--p.life < 0 || nx < 0 || ny < 0 || nx > w || ny > h) spawn(p) // old or off the edge: start again
}
g.stroke()
}
t += 0.0005 // the field drifts slowly while it draws
}
const sheet = () => { for (let i = 0; i < 300; i++) step() }
if (still) {
sheet() // reduced motion: draw one finished sheet and stop
addEventListener('resize', () => { size(); sheet() })
} else {
addEventListener('resize', size)
let n = 0
requestAnimationFrame(function frame() {
if (n < SHEET) step() // strokes pile up into a drawing
else { g.globalAlpha = 0.15; g.fillStyle = PAPER; g.fillRect(0, 0, w, h); g.globalAlpha = 1 }
if (++n === SHEET + FADE) { size(); parts.forEach(spawn); t = Math.random() * 100; n = 0 } // a new field
requestAnimationFrame(frame)
})
}
</script>A starting point written for this page, free to use in your own projects. The skills below build the finished version with checks, fallbacks and a recorded video.
What to get right on a real page
01
Batch the strokes
One stroke() call per particle gets slow in the thousands. Add every segment of one color to a single path and stroke it once per frame, as the starter does.
02
Watch for ghost trails
Fading a canvas with a faint fill each frame never quite reaches the background color, and leaves gray or tinted ghosts of old lines. Fade with a stronger fill, or keep the strokes and clear the canvas between sheets.
03
Leave empty paper
A field drawn everywhere turns into gray wool. Cap the number of strokes, spawn particles only where a second, slower noise allows, and keep the area behind the headline calm.
04
Seed it
Use a seeded random generator for the noise and the particles, so the same seed always draws the same picture. Then you can choose a favorite and keep it.
05
Hold a still frame for reduced motion
Respect prefers-reduced-motion: show one finished frame instead of the loop. A visitor who turned motion off still gets the look.
Skills that build a flow field
A finished hero that draws a flow field in sumi ink, with your colors, fonts, logo and words, and a frame-exact 1080p video for launch posts.
- Ink Flow HeroClaude CodeCodexCursor+3 more agents
Motion Heroes · Pro
Ink Flow Hero
Animated hero section · page + embed + 1080p video
A hanging scroll of washi paper beside your headline fills with sumi brush strokes as a few thousand particles follow a seeded flow field: gradient noise bent by two to four pressure centers, so the ink swirls into isobars like a weather map. Each stroke swells and lifts like a brush, and every few seconds the next seed draws a new sheet over the last. You set the particles, the field scale, the pull of the pressure centers, the ink load, and a framed scroll or open ink.
Worth knowing: The strokes are abstract: they will not spell lettering or a logo, and the field is generated, not real weather.
Needs: Node.js 18+, Chrome (or Chromium/Edge) for the checks and recording, ffmpeg for the video
See what Ink Flow Hero makes
Install
Build a flow field with your agent
Each skill is a SKILL.md folder, so it installs the same way in Claude Code, Codex, Cursor and other agents. The example below uses Ink Flow Hero; each skill page has its own prompt.
1. Paste the install prompt
Your agent runs the installer. The first time, it shows a sign-in link so you can approve it in the browser.
Install the "Ink Flow Hero" skill from Lookbk (https://lookbk.dev/skills/ink-flow-hero). 1. Run: curl -fsSL https://lookbk.dev/cli.js | node - add ink-flow-hero 2. If it prints a sign-in link, show it to me. When I say I approved it, run the same command again. 3. When the install finishes, read the SKILL.md it installed and tell me what you need from me to make the first result.
Or run it in a terminal
curl -fsSL https://lookbk.dev/cli.js | node - add ink-flow-hero2. Start a new session
Agents load skills when a session starts. The installer writes the folder where your agent looks:
- Claude Code
- ~/.claude/skills/ink-flow-hero
- Codex
- ~/.agents/skills/ink-flow-hero
- Cursor
- ~/.agents/skills/ink-flow-hero
3. Ask for the result
Describe it in plain words, for example:
Build an Ink Flow hero for Ostrel Editions' Isobar series: washi paper, indigo accent, headline 'Weather, in sumi.', seal 等圧線図, and record a 12-second video.
Another agent? Install a skill in any agent
FAQ
Questions
Still stuck? The guides cover installing, agents and licensing in more detail.
Read the guidesWhat is a Perlin noise flow field?
A flow field whose angles come from Perlin noise. Because Perlin noise changes smoothly, nearby points get similar angles and particles trace long, coherent curves. Value noise, as in the starter, and simplex noise work the same way.
How do I make a flow field in p5.js?
The same way: noise(x * scale, y * scale) returns a value from 0 to 1, which you turn into an angle and then a step with p5.Vector.fromAngle. Each particle adds the step to its position and draws a line. p5’s noise() is Perlin noise, so you do not need your own.
How many particles do I need?
A few hundred trace clean, separate lines; a few thousand fill the canvas with texture. Start around 1,000 to 2,000 for a full-screen hero and batch the strokes. Ink Flow Hero takes 400 to 5,000 and keeps the same density at any size.
Is a flow field heavy on phones?
A couple of thousand short lines a frame on a 2D canvas is fine on most phones, as long as they are batched into a few paths and the device pixel ratio is capped at 2. Stop drawing when the hero is off screen.
Can I get the flow field as a video?
Yes. Ink Flow Hero records a 1080p MP4 and a poster, stepped frame by frame, and the same seed and time always draw the same picture.
