You can just make pictures
The page you are reading is already painting. Drag on it.
That is a few thousand particles riding an invisible wind, drawn by p5.js, running inside the page instead of in a screenshot of one. It is one file. There was nothing to install to make it, and there is nothing to install to remix it. By the end of this post you will have built it in four pieces, and each piece is a live sketch you can touch before you read a word about it.
If you make things — paint, print, animate, VJ, teach a room of students who do — you have probably been told that code art starts with a toolchain: a package manager, a bundler, a project folder, a terminal you keep open. It doesn't. A vibe is a p5 sketch with a URL, an audience, and (in the next post) a memory. This post is the sketch.
Step 1 — press play
Here is the whole model of p5, and it is the last piece of theory in this
post. setup() runs once. draw() runs about sixty times a second, forever.
Anything you draw in draw() gets drawn again on the next frame, so a still
picture is a loop that happens to draw the same thing every time, and
animation is a loop where something changed.
jsfunction setup() {
createCanvas(640, 360);
}
function draw() {
background(20);
circle(mouseX, mouseY, 40);
}
Five lines: a dark canvas and a circle that follows your cursor. mouseX
and mouseY are just numbers p5 keeps current for you.
Those pixels are yours alone. The page hands p5 a box and gets out of the way — no framework is going to repaint over your canvas, and nothing you draw leaks out of it.
One thing you will notice the moment you open a demo rather than read it.
Every snippet in this post is written the way p5 is taught everywhere —
bare setup(), draw(), circle() — because that is the form you will
meet in every tutorial and every example you go on to read. The demos wrap
that sketch in a few lines that hand p5 its box and clean up after it, and
inside the wrapper the calls hang off a sketch handle: p.circle(...),
p.noise(...). Same functions, same ideas, one prefix.
Get posts like this in your inbox
One email field. Real updates. No algorithm required.
So read the snippets for the idea and open the demo for the file. The third post in this series is about that wrapper — what it does, and why in a page like this one it is not optional.
Step 2 — a thousand of anything
One dot is a demo. A thousand is a medium. A particle here is a plain object — a position and a velocity, four numbers:
jsparticles.push({
x: random(width),
y: random(height),
vx: random(-1, 1),
vy: random(-1, 1),
});
Every frame, add each particle's velocity to its position, and send anything that leaves one edge back in the other side. That is the whole simulation, and on its own it looks like what it is: a thousand dots jittering.
Now the trick that turns dots into ink. Instead of wiping the canvas clean
every frame with background(), paint a nearly-transparent rectangle over
the whole thing:
jsfunction draw() {
fill(20, 20, 20, 12); // the last number is opacity — almost see-through
rect(0, 0, width, height);
for (const p of particles) {
p.x += p.vx;
p.y += p.vy;
circle(p.x, p.y, 3);
}
}
Each frame dims what was there by a few percent instead of erasing it, so motion leaves a trail that fades as it ages. That one change is most of what people mean when they say a sketch "looks generative": the picture is no longer what you drew this frame, it is the history of everything that moved. Nudge the opacity from 12 down to 4 and the trails get long and smoky; push it to 60 and they snap back to dots. That number is your first knob.
Step 3 — the field
Random velocities give you noise in the bad sense. Replace them with a field: at every point on the canvas there is a direction, and a particle simply goes whichever way the ground under it points.
The direction comes from noise() — Perlin noise, a function that returns a
smooth, wandering value between 0 and 1, where nearby inputs give nearby
outputs. Feed it a position, treat the number it returns as an angle, and
step that way:
jsconst a = noise(p.x * scale, p.y * scale, t) * TWO_PI * 2;
p.x += cos(a) * speed;
p.y += sin(a) * speed;
scale is how fast the weather changes as you walk across the canvas —
small numbers give long lazy currents, larger ones a choppy sea. t creeps
up by a hair every frame, which is the third dimension of the noise: the
wind itself drifts, so the picture never settles into one groove.
One thing the field does that random velocities never did: it herds. A smooth field has sinks, and particles that ride it all end up in the same few, so a sketch left alone for fifteen seconds drains into a couple of bright knots with the rest of the canvas empty. The fix is a line — give each particle a lifetime, and respawn it somewhere random when it runs out:
jsp.life -= 1;
if (p.life <= 0) { p.x = random(width); p.y = random(height); p.life = random(40, 220); }
Constant rain, in other words. The picture you want is the field being sampled everywhere, continuously, not the field's final answer.
The demo below has a switch. Turn the field on and you see it directly — a
grid of little arrows, each one asking noise() about its own spot. Turn it
off and the same field is invisible again, with a few thousand particles
reporting it. Both views read the identical function; the arrows are just
the field admitting what it is.
That is the shift worth taking from this post. You are not drawing the picture. You are describing the weather, and the picture is what happens to things caught in it.
Step 4 — knobs
Scroll back to the top of this post. That painter is these three steps with four numbers pulled out where your hands can reach them, and you now know what every one of them does.
Palette picks the five colours the particles draw with. Grain is
scale from step 3 — the size of the weather. Speed is how far a
particle moves per frame, which also sets how long its trail is before the
fade catches up. And symmetry is the one that is not in the simulation
at all: every step a particle takes is drawn N times, each rotated a little
further around the centre. At 1 you get the raw field. At 6 you get a
kaleidoscope — same thousand particles, same wind, drawn six times over.
Four sliders are the difference between a demo and an instrument. The demo shows you what the programmer found. The instrument lets you go looking.
Remix it
The whole painter is one file, and it is public. Open it, press remix, and you are standing in your own copy with your own URL: change the palette to the colours of the thing you are actually working on, swap the circle for a brush shape, feed the field a photograph instead of noise. There is no project to set up first, because there was never a project. There is a sketch, and now it is yours.
One catch, and the next post in this series fixes it with a single hook: reload the page and the thing you just made is gone.
Make a picture of your own
Remix any sketch in this post, or describe the one you wish existed.
Start building →