Learn Creative Coding Series):Before we can make beautiful things, we need to understand the canvas we're drawing on.
Last episode we threw random circles at the screen and it looked cool. But we were mostly guessing. This time we're going to understand exactly what we're telling the computer to draw, and where.
The p5.js canvas is a grid. The top-left corner is position (0, 0). X increases to the right. Y increases downward.
That Y-going-down thing trips people up. In math class, Y goes up. In computer graphics, Y goes down. Just accept it and move on. :-)
function setup() {
createCanvas(400, 400);
background(240);
// mark the origin
fill(255, 0, 0);
noStroke();
ellipse(0, 0, 10, 10); // top-left corner
// mark the center
fill(0, 0, 255);
ellipse(200, 200, 10, 10); // center
// mark the bottom-right
fill(0, 180, 0);
ellipse(400, 400, 10, 10); // only a quarter visible!
}
Run this. Red dot in the corner, blue in the middle, green barely visible in the bottom-right. Everything in p5.js uses this coordinate system.
p5.js gives you a bunch of shape functions. Here are the ones you'll use 90% of the time:
rect(x, y, width, height);
By default, x and y are the top-left corner. You can change this with rectMode(CENTER) to make x and y the center point instead.
function setup() {
createCanvas(400, 400);
background(240);
fill(100, 150, 255);
rect(50, 50, 120, 80); // top-left mode (default)
rectMode(CENTER);
fill(255, 150, 100);
rect(300, 200, 120, 80); // center mode
}
ellipse(x, y, width, height);
Ellipse draws from the center by default (opposite of rect - I know, it's weird). Equal width and height gives you a circle.
function setup() {
createCanvas(400, 400);
background(240);
fill(200, 100, 200);
ellipse(200, 200, 150, 150); // circle
fill(100, 200, 150);
ellipse(200, 200, 200, 80); // oval
}
line(x1, y1, x2, y2);
Lines don't have fill, only stroke. The strokeWeight() function controls thickness.
function setup() {
createCanvas(400, 400);
background(240);
stroke(50);
strokeWeight(1);
line(50, 50, 350, 350); // thin diagonal
stroke(200, 50, 50);
strokeWeight(5);
line(50, 350, 350, 50); // thick diagonal, other direction
}
triangle(x1, y1, x2, y2, x3, y3);
Three points. That's it.
function setup() {
createCanvas(400, 400);
background(240);
fill(255, 200, 50);
triangle(200, 50, 50, 350, 350, 350);
}
arc(x, y, w, h, start, stop);
Arcs use radians, not degrees. p5.js gives you constants: PI, HALF_PI, QUARTER_PI, TWO_PI. Or use radians(degrees) to convert.
function setup() {
createCanvas(400, 400);
background(240);
fill(100, 200, 255);
arc(200, 200, 200, 200, 0, PI + HALF_PI); // pac-man-ish
}
Every shape has two visual properties: fill (the inside color) and stroke (the outline).
fill(r, g, b); // set fill color
noFill(); // no fill, just outline
stroke(r, g, b); // set stroke color
noStroke(); // no outline
strokeWeight(n); // outline thickness in pixels
These work like a "state machine" - once you set a fill, everything after uses that fill until you change it.
function setup() {
createCanvas(400, 400);
background(240);
// red fill, no outline
fill(220, 60, 60);
noStroke();
ellipse(100, 200, 100, 100);
// no fill, blue outline
noFill();
stroke(60, 60, 220);
strokeWeight(3);
ellipse(200, 200, 100, 100);
// green fill + dark outline
fill(60, 200, 60);
stroke(20);
strokeWeight(2);
ellipse(300, 200, 100, 100);
}
RGB colors go from 0 to 255. You can add a fourth value for alpha (transparency):
fill(255, 0, 0, 128); // red at 50% opacity
Alpha is incredibly useful for creative coding. Layering semi-transparent shapes creates depth and richness that opaque shapes can't.
function setup() {
createCanvas(400, 400);
background(20);
noStroke();
// layer 20 semi-transparent circles
for (let i = 0; i < 20; i++) {
fill(
100 + i * 7, // red increases
50,
200 - i * 5, // blue decreases
40 // low opacity
);
ellipse(
150 + i * 8,
200 + sin(i) * 50,
80 + i * 3,
80 + i * 3
);
}
}
See how the overlapping creates colors that don't exist in your palette? That's the beauty of alpha blending. This is something you'll use in almost every creative coding project.
When you pass one number to fill(), stroke(), or background(), it's a grayscale value:
fill(0); // black
fill(128); // middle grey
fill(255); // white
Two values means grayscale + alpha:
fill(0, 100); // black at ~40% opacity
Enough theory. Let's combine what we know into a simple geometric composition:
function setup() {
createCanvas(500, 500);
background(245, 240, 235); // warm off-white
noStroke();
// big circle, bottom-left
fill(35, 60, 120, 200);
ellipse(150, 380, 280, 280);
// overlapping rectangle, top-right
fill(200, 75, 50, 180);
rect(220, 40, 240, 300);
// small circle on top of the rectangle
fill(240, 190, 50, 220);
ellipse(340, 200, 140, 140);
// thin lines cutting across
stroke(20, 80);
strokeWeight(1);
for (let i = 0; i < 12; i++) {
line(0, i * 45, 500, i * 45 + 20);
}
// accent triangle
noStroke();
fill(245, 240, 235, 180);
triangle(80, 80, 200, 40, 140, 180);
}
This gives you a layered geometric composition with overlapping shapes, transparency, and some visual rhythm from the lines. Not random, not messy - designed. Change some numbers and you get a completely different composition. That's the whole idea.
rect(), ellipse(), line(), triangle(), arc()fill() and stroke() control appearance, work like a state machineNext episode we're adding motion. Things are about to get fun - we'll learn how draw() actually works as an animation loop and use sin() and cos() to make things move in ways that feel organic.
Sallukes! Thanks for reading.
X