p5.js Session 15: Animation
Every sketch in this series had ended with noLoop() in setup(). It draws once and stops. Take it out and draw() repeats instead, over and over, for as long as the sketch is open.
On its own that changes nothing you can see. The same circle drawn in the same place thirty times a second looks exactly like a circle drawn once. Movement only appears when something is different between one pass and the next, and the simplest thing to make different is a position.
draw() runs over and over
A circle, a position, and two pixels added to that position on every pass. The canvas is painted black once, in setup(), and never touched again.
let x = 40;
let y = 300;
let speed = 2;
function setup() {
createCanvas(600, 600);
frameRate(30);
noStroke();
background(0);
fill('#FB8B24');
}
function draw() {
if (x < width - 40) {
x = x + speed;
}
circle(x, y, 80);
}
function keyPressed() {
if (key === 'g' || key === 'G') {
saveGif('ball-trail', 10);
}
}
noLoop()is gone, sodraw()repeats instead of running once.frameRate(30)sets how often it repeats, thirty times a second, smooth but still slow enough to follow.x = x + speedis the whole of the movement. Each pass adds 2 to the position, then the circle is drawn at the new spot.background(0)is insetup(), so the black is painted once and never repainted. Every circle that gets drawn stays on the canvas.- The
ifstops the ball when its edge reaches the right wall.widthis the canvas width, and 40 is half the ball. saveGif('ball-trail', 10)records the sketch itself. Session 14 saved a still withsaveCanvas(), this saves the movement.- The trigger is the same
keyPressed()you already used for saving. Pressgand p5.js captures the next ten seconds as a GIF. - Take those four lines out once you have the file, or it records again every time you press play.
That bar is not a ball that moved. It is around 260 separate circles, each drawn two pixels further right than the one before, all still sitting there. It is the clearest picture you will get of what draw() is actually doing.
Clearing the canvas each frame
Move one line and the whole thing changes. background(0) comes out of setup() and goes to the top of draw(), so the canvas is wiped before every circle.
let x = 40;
let y = 300;
let speed = 2;
function setup() {
createCanvas(600, 600);
frameRate(30);
noStroke();
fill('#FB8B24');
}
function draw() {
background(0);
if (x < width - 40) {
x = x + speed;
}
circle(x, y, 80);
}
function keyPressed() {
if (key === 'g' || key === 'G') {
saveGif('ball-clear', 10);
}
}
- The only difference between this and the last script is which function
background(0)sits in. Insetup()it paints once. Indraw()it repaints before every circle. - Nothing is moving even now. Each frame is a fresh circle in a slightly different place, thirty times a second, and your eye does the rest.
- The ball still stops at the wall. With the trail gone there is nothing left on the canvas but a ball sitting against the edge, waiting.
Which raises the obvious question. What if it did not stop?
Five balls, bouncing
One ball needs an x, a y, and a speed on each axis. Five balls need five of each, so all four go into arrays, the same way the palette did in Session 13.
Five fiery colours, one per ball. One starts in the middle of the canvas, the other four in the corners, and every one of them is handed a random direction to set off in.
let palette;
let x = [];
let y = [];
let xSpeed = [];
let ySpeed = [];
let ballSize = 80;
function setup() {
createCanvas(600, 600);
frameRate(30);
noStroke();
palette = ['#5F0F40', '#9A031E', '#FB8B24', '#E36414', '#0F4C5C'];
x = [300, 40, 560, 40, 560];
y = [300, 40, 40, 560, 560];
for (let i = 0; i < 5; i++) { xSpeed[i] = randomSpeed(); ySpeed[i] = randomSpeed(); }
}
function randomSpeed() {
let s = random(1.5, 3.5);
if (random(1) < 0.5) { s = -s; }
return s;
}
function draw() {
background(0);
let r = ballSize / 2;
for (let i = 0; i < 5; i++) {
x[i] = x[i] + xSpeed[i];
y[i] = y[i] + ySpeed[i];
if (x[i] < r) { x[i] = r; xSpeed[i] = random(1.5, 3.5); ySpeed[i] = randomSpeed(); }
if (x[i] > width - r) { x[i] = width - r; xSpeed[i] = -random(1.5, 3.5); ySpeed[i] = randomSpeed(); }
if (y[i] < r) { y[i] = r; ySpeed[i] = random(1.5, 3.5); xSpeed[i] = randomSpeed(); }
if (y[i] > height - r) { y[i] = height - r; ySpeed[i] = -random(1.5, 3.5); xSpeed[i] = randomSpeed(); }
fill(palette[i]);
circle(x[i], y[i], ballSize);
}
}
function keyPressed() {
if (key === 'g' || key === 'G') {
saveGif('bouncing-balls', 10);
}
}
x[i]andy[i]hold ball numberi’s position.xSpeed[i]andySpeed[i]hold how far that ball moves on each axis per frame. Five balls, four arrays, one loop.ris half a ball’s width. Positions are measured from the centre of a circle, so a ball has reached the left wall when its centre israway from it, not 0.- Each bounce puts the ball back exactly on the wall first, so it cannot get stuck outside the canvas.
- Then it hands out new speeds, one back into the canvas and one along the wall.
randomSpeed()is a function of our own, the idea from Session 9. It picks a number between 1.5 and 3.5, then makes it negative half the time.
A real bounce sends a ball away at the mirror of the angle it arrived at. This one does not. It picks a fresh random direction pointing back into the canvas, which is why the five never settle into a rhythm and why the whole thing reads more like scattering than like billiards. That is a choice rather than a mistake, and it is worth knowing which one you are looking at.
The scripts in this session were written for p5.js 2.3.2, the version the web editor runs by default.
Next session, we hand the controls over to the mouse: sketches that answer back to where the pointer is and where it clicks.