p5.js Session 8: Push and Pop
Last session’s flower lived on its own in the centre of the canvas. This time we want four of them, one in each corner. That’s where translate() and rotate() start to show their real behaviour: every change to the canvas carries over into everything drawn after it.
Translating from corner to corner
We want four flowers, one in each corner of the square: 150, 150, then 450, 150, then 450, 450, then 150, 450. The simplest option is to repeat last session’s script four times, changing only the translate() coordinates. Let’s try that and see what actually happens.
function setup() {
createCanvas(1200, 1200);
angleMode(DEGREES);
noLoop();
noStroke();
}
function draw() {
background(220);
fill(0);
rect(0, 0, 600, 600);
translate(150, 150);
for (let i = 0; i < 8; i++) {
rotate(45);
fill(0, 0, 255);
ellipse(60, 0, 90, 35);
}
fill(255, 0, 0);
circle(0, 0, 25);
translate(450, 150);
for (let i = 0; i < 8; i++) {
rotate(45);
fill(0, 0, 255);
ellipse(60, 0, 90, 35);
}
fill(255, 0, 0);
circle(0, 0, 25);
translate(450, 450);
for (let i = 0; i < 8; i++) {
rotate(45);
fill(0, 0, 255);
ellipse(60, 0, 90, 35);
}
fill(255, 0, 0);
circle(0, 0, 25);
translate(150, 450);
for (let i = 0; i < 8; i++) {
rotate(45);
fill(0, 0, 255);
ellipse(60, 0, 90, 35);
}
fill(255, 0, 0);
circle(0, 0, 25);
}
Only the first flower lands where intended, at 150, 150. Each translate() after it moves from wherever the origin already sits, not from the canvas corner. The second call adds 450, 150 onto the first flower’s position and lands at 600, 300; the third and fourth compound further still, and the last flower ends up almost off the canvas. Every translate and rotate call carries the ones before it.

push() and pop()
push() saves the current drawing state, position, rotation, fill, stroke, like a bookmark. pop() returns to that bookmark. Anything between the two, any translate() or rotate(), is undone the moment pop() runs.
function setup() {
createCanvas(1200, 1200);
angleMode(DEGREES);
noLoop();
noStroke();
}
function draw() {
background(220);
fill(0);
rect(0, 0, 600, 600);
push();
translate(150, 150);
for (let i = 0; i < 8; i++) {
rotate(45);
fill(0, 0, 255);
ellipse(60, 0, 90, 35);
}
fill(255, 0, 0);
circle(0, 0, 25);
pop();
push();
translate(450, 150);
for (let i = 0; i < 8; i++) {
rotate(45);
fill(0, 0, 255);
ellipse(60, 0, 90, 35);
}
fill(255, 0, 0);
circle(0, 0, 25);
pop();
push();
translate(450, 450);
for (let i = 0; i < 8; i++) {
rotate(45);
fill(0, 0, 255);
ellipse(60, 0, 90, 35);
}
fill(255, 0, 0);
circle(0, 0, 25);
pop();
push();
translate(150, 450);
for (let i = 0; i < 8; i++) {
rotate(45);
fill(0, 0, 255);
ellipse(60, 0, 90, 35);
}
fill(255, 0, 0);
circle(0, 0, 25);
pop();
}
Each push() / pop() pair works in its own bubble. Whatever happens to the canvas inside it gets discarded the moment pop() runs, so the next block starts fresh from the canvas’s real origin. That’s why all four flowers land exactly on their corners this time.

It works, but look at what we just did: the exact same dozen lines, copied four times, with only the translate coordinates changed.
In Session 9 we fix that: functions let us name this block of code once and call it as often as we like.