Changes over time

← back to Creative Computing

by Allison Parrish

In this tutorial, I’ll show you how to make a simple p5.js sketch that changes as time goes on. Before we talk about that, though, I need to explain a little bit about how time works in Processing.

Setup and draw

In all the sketches I’ve shown you so far, there were two weird sections of the program: the stuff in function setup() { ... } and the stuff in function draw() { ... }. I didn’t tell you yet exactly what those parts of the code do, and it’s going to be a while before I’ve taught you enough that you can actually fully understand what those parts of the code “mean” and why they look the way they do.

But to get us past the next hurdle, here’s the short way of explaining it. Any statements you put in setup() are run once, at the beginning of the program. Any statements you put in draw() are run over and over again, typically many times every second.

“Wait, huh?” you say. “What do you mean? My sketches don’t look like they’re getting drawing over and over again to me. It just looks like it’s exactly the same static image.” And yes, you’re right! All of the sketches I’ve shown you so far do exactly the same thing each time they’re run, so there’s no appearance of change when you run the sketch.

But rest assured, the code in draw() is getting run over and over again. You can test this by running the following code:

function setup() {
    createCanvas(400, 400);
}

function draw() {
    console.log("mark"); 
}

Keep an eye on the debug area (beneath where you type the code). You’ll see the word “mark” appear many times very quickly.

What’s happening here? The console.log() function, as a reminder, is a function whose only purpose is to print a particular value to the debug area. Everything inside of draw(), as mentioned above, gets executed many times per second. So the end result of the code above is massive repetition of the same console.log() function call, over and over again.

Frames

The name in computer animation for a single call of the function that draws something to the screen is a “frame.” Processing provides two handy ways to work with frames: the function frameRate() and the built-in variable frameCount.

The frameRate() function allows you to set how often the code in draw() will run. This is called “setting the frame rate.” Try running this sketch:

function setup() {
    createCanvas(400, 400);
    frameRate(1);
}

function draw() {
    console.log("mark"); 
}

The call to frameRate() should be in the setup() part of your code, not draw(), since you only need to set the frame rate once. The number you put between the parentheses determines how many times per second the code in draw() gets run. You’ll see that the above sketch still prints the word mark but does so much more slowly.

The frameCount variable is built into Processing. You don’t have to define it yourself—Processing defines it for you, before your sketch begins. The frameCount variable is special in that it changes on every frame: the first time the code in draw() is executed, frameCount is equal to zero; the second time the code in draw() is executed, frameCount is equal to one; etc. This sketch is exactly like the one above, but instead of printing the word “mark,” it prints the current value of the frameCount variable:

function setup() {
    createCanvas(400, 400);
    frameRate(1);
}

function draw() {
    console.log(frameCount); 
}

If you run this sketch in the p5 web editor, you’ll see the sketch slowly counting up, one number per second, outputting the numbers in the console.

(Of course, you can change the number in frameRate() to make the numbers go faster.)

Animation with frameCount

The frameCount variable is just like any other variable, in that you can use it in expressions and function calls. Because frameCount changes on every frame, you can exploit its value to create simple animation. Here’s an example:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noFill();
  stroke(255);
  strokeWeight(8);
  ellipse(frameCount, 200, 100, 50+(frameCount/2));
}

You should see a small ellipse that starts at the left-hand side of the screen and moves slowly to the right, gradually getting taller.

EXERCISE: Use the frameCount variable in other function calls. Use the frameCount variable as part of expressions. See what happens!

Retaining the content of the previous frame

Here’s a version of the above sketch with one important difference. See if you can pick it out and reason about what’s happening.

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
  background(50);
}
function draw() {
  noFill();
  stroke(255);
  strokeWeight(8);
  ellipse(frameCount, 200, 100, 50+(frameCount/2));
}

That’s super weird! It looks like each frame is being drawn over the previous. What gives?

The first thing to know is that Processing doesn’t automatically clear the canvas on each frame. By default, whatever was on the canvas at the end of the previous frame remains on the canvas in the next frame.

So the difference between the two sketches lies in where we called the background() function. In the example above, the call to background() happens in the setup() code, which means that it only happens once. In the draw() code, there is no call to background(), so the background doesn’t get “reset” to a flat color on each frame.

If you don’t redraw the background in each frame, the effect is to make it seem like the sketch is being drawn on top of itself, over and over again. If you do call background() in each frame, the effect looks more like animation. You can use both methodologies to great effect!

Simple oscillation

It’s pretty cool to be able to make animations, but there’s a problem with frameCount: it grows linearly, so there’s no clear way to make an animation that appears to loop: you can only make something that grows bigger or moves in one direction. In this section, I’m going to show you a few simple strategies for oscillation, or making something that goes back and forth between different states, using only the linear frameCount value as the source of change.

Modulo

In mathematics, there’s an operation called “modulo.” This operation gives you the remainder of the result of dividing one integer by another. For example, the result of 11 modulo 5 is the remainder of dividing eleven by five, or one. (Five times two is ten, and you need one more to make eleven.)

In Javascript, there’s an operator for modulo that works just like the other arithmetic operators in its syntax: %. For example, put this statement into a p5.js editor window and see what happens:

console.log(11 % 5)

You should see 1 in the output window. Try different values and see what happens!

What’s interesting for our purposes is that we can use the modulo operator to create a very simple repeating counter. Our ability to do this comes from the fact that the modulo operation produces the following pattern:

0 % 5 => 0 (zero divided by five is zero, with a remainder of zero)
1 % 5 => 1 (one divided by five is zero, with a remainder of one)
2 % 5 => 2
3 % 5 => 3
4 % 5 => 4
5 % 5 => 0 (five divided by five is one, with a remainder of zero)
6 % 5 => 1 (six divided by five is one, with a remainder of one)
7 % 5 => 2 (etc.)
...

In other words, if you have a constantly increasing value on the left side of the modulo operator, and a constant value on the right side, the result of evaluating the modulo expression creates a series of repeating values from zero up to the value on the right side of the operator.

We can use this property of the modulo operator to create “loops” that repeat every n frames, where n is a number we can choose. For example, here’s a sketch that creates an ellipse that grows and resets to its original size every thirty frames:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noFill();
  stroke(255);
  strokeWeight(8);
  ellipse(200, 200, (frameCount % 30)*10, 50);
}

And here’s a sketch that draws a circle moving from left to right, which appears to “reset” to the left side of the screen when it reaches the right side. Additionally, there’s a second oscillation that effects the stroke weight:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noFill();
  stroke(255);
  strokeWeight(4 + (frameCount % 16));
  ellipse(frameCount % 400, 200, 100, 100);
}

EXERCISE: Create a sketch with multiple oscillations of different periods using a modulo of the frameCount.

Sine

The modulo technique is great, but it can only easily produce loops that grow linearly and loop abruptly. An easy technique for making animations that appear to grow and recede smoothly is to calculate the sine of the frameCount variable.

Right now, I’m not going to go into detail about what the “sine” function is or how it works. For our purposes, the main purpose of calculating a value’s sine is to use that value to create oscillations. The sine function is available in p5.js using the sin() function. Unlike other functions we’ve discussed so far, the purpose of sin() isn’t to draw something to the screen, but to take a single value and return another value after applying a mathematical operation to it.

The sin() function takes a single parameter, and evaluates to a number from -1 to 1. Evaluating the sin() function with various values looks like this:

sin(0) = 0
sin(0.39) = 0.38
sin(0.78) = 0.70
sin(1.17) = 0.92
sin(1.57) = 1
sin(1.96) = 0.92
sin(2.35) = 0.70
sin(2.74) = 0.38
sin(3.14) = 0
sin(3.53) = -0.38
sin(3.92) = -0.70
sin(4.31) = -0.92
sin(4.71) = -1
sin(5.10) = -0.92
sin(5.49) = -0.70
sin(5.89) = -0.38
sin(6.28) = 0

In other words, the sine of zero is zero; the sine of pi/2 (~1.57) is one; the sine of pi is zero; the sine of 3/2pi (~4.71) is negative one; and the sign of 2*pi (~6.28) is 0 again. Increasing values passed to sin() will still result in values between -1 and 1.

So what if we pass the frameCount variable to sin()? Well, you’d get something like this:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noFill();
  stroke(255);
  strokeWeight(8);
  ellipse(200, 200, 100+sin(frameCount), 100);
}

… which isn’t very impressive. The circle is just… jiggling a little. That’s because the sin() function always returns a value from -1 to 1. If we want this change to be really visible, we need to multiply the result so that the value will make a difference of more than one pixel either way:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noFill();
  stroke(255);
  strokeWeight(8);
  ellipse(200, 200, 100+(sin(frameCount)*50), 100);
}

Okay, that’s better! But it’s a bit, uh, frenetic. That’s because the sin() function produces its entire cycle of values counting up to around six, and frameCount moves counts past six very quickly (at the default frame rate, ten times a second!). To make the oscillation slower, we need to divide the frameCount value by some other value. Let’s make it ten times slower by dividing by ten:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noFill();
  stroke(255);
  strokeWeight(8);
  ellipse(200,200,100+(sin(frameCount/10)*50),100);
}

Most of the time you use the sin() function, you should use it in combination with two other values, which determine the amplitude of the oscillation (i.e., how big the numbers get) and the frequency of the oscillation (i.e., how fast it goes). It’ll look something like this:

x + (sin(frameCount / y) * z)

… where x, y and z are all numbers. Making y bigger will make the oscillation slower; making z bigger will make the oscillation larger. The x value is the oscillation’s center point, i.e., what value is the “resting place” of the oscillation.

Repetition and variation

Here’s a sketch that makes a series of circles that move across the screen, using a sin() to control their horizontal positions:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noStroke();
  fill(255);
  for (let i = 0; i < 5; i++) {
    ellipse(200 + sin(frameCount/10) * 20,
      100+(i*50), 40, 40);
  }
}

And here’s a slightly more sophisticated example, which uses the loop variable i to introduce subtle variations to each circle:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noStroke();
  for (let i = 0; i < 5; i++) {
    fill(frameCount % 255);
    ellipse(
      200+(sin(frameCount/(i+10))*(i+20)),
      100+(i*50),
      40,
      40);
  }
}

The tricky part of this example is the following expression:

200+(sin(frameCount/(i+10))*(i+20))

To better understand this expression, try translating it into English, then try calculating what its value would be for certain values of frameCount and i.

Changing variables over time

In previous examples, the variables we’ve used only ever have one value: the value that we set for them initially in the part of the code before setup(). But the value of variables can change over the course of the program: once we’ve set a variable, we can direct Javascript to overwrite the current value of that variable with something else. You can use this property of Javascript to write sketches that change over time in a slightly more sophisticated manner than is possible using just the frameCount variable.

As you’ll recall, the syntax for declaring a variable looks like this:

let x = expr;

… where x is the name of the variable, and expr is some expression whose value should be assigned to x. You can change the value of a variable using the following syntax:

x = expr;

… where, again, x is the name of a variable, and expr is an expression whose result will be assigned to x.

Here’s a sketch that declares two variables, foo and bar, and then makes changes to those variables inside the draw() code.

► run sketch ◼ stop sketch
let foo = 0;
let bar = 400;

function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noFill();
  stroke(255);
  strokeWeight(8);
  ellipse(foo, 150, 45, 45);
  ellipse(bar, 250, 45, 45);

  foo = foo + 1;
  bar = bar - 1;
}

As you can see, on each frame, the code in this sketch adds one to foo and subtracts one from bar.

Writing an expression to simply take the current value of a variable, and then assign back to it that value plus or minus one is so common that there’s a shortcut for it. This expression:

foo = foo + 1;

can be rewritten as:

foo += 1;

You can use values other than 1 in this expression as well.

Here’s an example that foregoes a lot of the weird arithmetic we had to do in the above examples with the sin() function by incrementing a value by a small amount on each frame (instead of dividing frameCount):

► run sketch ◼ stop sketch
let xpos = 0;

function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noFill();
  stroke(255);
  strokeWeight(8);
  ellipse(200 + sin(xpos) * 100, 200, 45, 45);
  xpos += 0.05;
}

Let’s add in a Y position just for fun:

► run sketch ◼ stop sketch
let xpos = 0;
let ypos = 0;

function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noFill();
  stroke(255);
  strokeWeight(8);
  ellipse(
    200+sin(xpos)*100,
    200+sin(ypos)*100,
    45, 45);
  xpos += 0.05;
  ypos += 0.04;
}

EXERCISE: What if the amount by which you adjusted xpos and ypos on each frame was itself changing over time, instead of a static value?

Random changes

In my opinion, computer-generated art only really starts to get fun once it has the appearance of not being deterministic. So let’s introduce some randomness into our sketches.

Processing has a function called random() that evaluates to a random number between zero and one. See the reference page for more information and alternate ways to call the function. The numbers produced by the random function will change every time you run the sketch, and from one function call to the next.

You can use the random() function to draw shapes at random locations in the sketch:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
  background(50);
}
function draw() {
  noStroke();
  fill(random() * 255);
  ellipse(random()*400, random()*400, 50, 50);
}

You can call the random() function with two parameters, in which case it will return a random number between the values of the first and the second parameter. To create a shape that appears to “wander” around the screen:

► run sketch ◼ stop sketch
let xpos = 200;
let ypos = 200;

function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noFill();
  stroke(255);
  strokeWeight(8);
  ellipse(xpos, ypos, 45, 45);
  xpos += random(-2, 2);
  ypos += random(-2, 2);
}