Interaction

by Allison Parrish

In this tutorial, you’ll learn how to use p5.js to make sketches that react to user input (and other aspects of the world outside of your sketch).

Built-in Processing variables

I showed you how to create variables in your sketches as a way of making them more flexible: you can use the same value, over and over again. Processing provides some built-in variables that are available to your sketch without your having to define them. These variables give you information about things that would normally be outside the ken of your sketch.

I’ve already shown you one helpful built-in variable, frameCount, in a previous tutorial.

Width and height

The width and height variables always contain a value that corresponds to the width and height of your sketch (in pixels). Generally, this will be the same as the value that you specify in the createCanvas() function.

The width and height variables are useful because they allow you to make sketches that take into account the size of the canvas. For example, here are two versions of the same sketch, both different sizes, but which regardless display a circle that takes up exactly one third of the sketch window:

► run sketch ◼ stop sketch
function setup() {
    createCanvas(200, 200);
    noLoop();
}
function draw() {
    background(0);
    fill(255);
    ellipse(width/2, height/2, width/3, height/3);
}
► run sketch ◼ stop sketch
function setup() {
    createCanvas(400, 400);
    noLoop();
}
function draw() {
    background(0);
    fill(255);
    ellipse(width/2, height/2, width/3, height/3);
}

This relationship holds even if we make the sketch rectangular instead of a square:

► run sketch ◼ stop sketch
function setup() {
    createCanvas(400, 300);
    noLoop();
}
function draw() {
    background(0);
    fill(255);
    ellipse(width/2, height/2, width/3, height/3);
}

Mouse position

Processing provides two special variables, mouseX and mouseY, that contain the X and Y coordinates of the mouse cursor in the current frame. Remember that the code in draw() is running over and over again, very quickly: up to sixty times per second. A bit of pre-written code that is a part of the p5.js library calls this function over and over for you, and each time it does so, it updates the mouseX and mouseY variables with the current position of the mouse cursor on the screen. These two variables together make it easy to make your sketch react to user input in the form of mouse movement.

Here’s a quick example sketch that simply draws a circle underneath the current mouse position:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noFill();
  stroke(255);
  strokeWeight(8);
  ellipse(mouseX, mouseY, 45, 45);
}

Of course, you’re not limited to using the mouseX and mouseY to control the position of the elements that you draw. Here’s a sketch that uses the mouseX value to control the width of the stroke, and the mouseY value to control the size of the ellipse:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noFill();
  stroke(255);
  strokeWeight(mouseX / 10);
  ellipse(200, 200, mouseY, mouseY);
}

A classic Processing example sketch is to omit the call to background() in the draw() code while tracking the mouse, which makes a rudimentary drawing program:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
  background(50);
}
function draw() {
  noFill();
  stroke(255);
  strokeWeight(8);
  ellipse(mouseX, mouseY, 45, 45);
}

We can do one better than this by introducing some change over time and modifying the basic mechanics:

► run sketch ◼ stop sketch
let greenVal = 0;
function setup() {
  createCanvas(400, 400);
  background(50);
}
function draw() {
  noFill();
  stroke(255, sin(greenVal)*255, 255);
  strokeWeight(8);
  ellipse(mouseX, 400-mouseY, 45, 45);
  greenVal += 0.05;
}

Contingent behavior with if

When you’re dealing with input from a user, you often find yourself in the position of wanting your sketch to have two or more different behaviors. Under one circumstance, you want the sketch to behave in one way; under a second circumstance, you want your sketch to behave in another way.

Imagine, for example, that you want to make a sketch where a rectangle is displayed when the user has the mouse cursor in the lower half of the sketch. That is to say: if the mouse position is greater than half the width, then display the rectangle (and otherwise, display nothing).

In order to accomplish this task, we need some behavior of the sketch to be contingent. There needs to be a way to say “check to see if something is the case; if it is the case, then do this other thing.” Javascript provides a special syntax for just this occasion: the if statement.

Here’s a sketch that does exactly the task that I described above.

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  fill(255);
  if (mouseY > height/2) {
    rect(100, 100, 200, 200);
  }
}

The new bit here is the following three lines of code:

if (mouseY > height/2) {
    rect(100, 100, 200, 200);
}

This is an example of an if statement. Schematically, an if statement looks like this:

if (expr) {
    code-to-run
}

… where expr is a relational expression (discussed below) and code-to-run is one or more Javascript statements (e.g., function calls, for loops, even other if statements). If the relational expression evaluates to true, then the statements in code-to-run get executed. Otherwise, the statements are not executed.

Relational expressions

Relational expressions are a lot like the other kinds of expressions that we’ve looked at, in that they have the following structure:

expr operator expr

That is: an operator that needs to have an expression to either side. The difference between relational operators and other operators is that relational operators don’t evaluate to another number, but to one of two special values: true or false. Relational expressions are used to determine how two values relate to each other. Specifically: whether one value is greater than another, or whether the two values are equal.

The most common relational operators are listed below:

operator meaning
> greater than
< less than
== equal to
>= greater than or equal to
<= less than or equal to

Try running the following statements in the p5.js editor and check out the results:

console.log(15 > 10);
console.log((3 * 30) < (2 * 40));
console.log((3 + 5) * 2 == (10 +  6));

You should see this in the debug output:

true
false
true

Multiple ifs

You can have multiple if statements in the draw() section of your code. (In fact, you can put if statements anywhere you’d like—even inside for loops or other if statements!). Here’s an example sketch that draws a rectangle if the mouse is in the lower half of the sketch, and a circle if the mouse is on the right-hand side of the sketch:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noStroke();
  if (mouseY > height/2) {
    fill(255);
    rect(100, 100, 200, 200);
  }
  if (mouseX > width/2) {
    fill(50, 50, 240);
    ellipse(200, 200, 210, 210);
  }
}

You can also embed one if statement inside another. Here’s the same sketch, except it displays the circle only if the mouse is both on the right hand side of the screen and in the lower half of the screen:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noStroke();
  if (mouseY > height/2) {
    fill(255);
    rect(100, 100, 200, 200);
    if (mouseX > width/2) {
      fill(50, 50, 240);
      ellipse(200, 200, 210, 210);
    }
  }
}

Mouse clicks

Processing includes a special built-in variable called mouseIsPressed which holds the value true if the user is currently holding the mouse, and false otherwise. You can use this to make your sketch do different things depending on whether or not the user is holding down the mouse button.

Here’s an example that displays a circle only if the mouse button is pressed:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noFill();
  stroke(255);
  strokeWeight(8);
  if (mouseIsPressed) {
    ellipse(200, 200, 300, 300);
  }
}

Otherwise…

Let’s say that I’ve given you the following task: create a sketch that displays a circle at the top of the sketch when the mouse is in the top of the sketch, and a circle at the bottom on the sketch otherwise.

Your first attempt might look something like this:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noStroke();
  fill(255);
  if (mouseY < height/2) {
    ellipse(200, 100, 150, 150);
  }
  ellipse(200, 300, 150, 150);
}

But that’s not quite right! The bottom circle is getting displayed all of the time, not just when the relational condition in the if statement above it doesn’t evaluate to true. This example illustrates that any of the code you put after an if statement’s closing curly brace will execute regardless of whether or not the if statement succeeded.

You could get around this by having two different if statements, one to check if the mouse position is in the top half, and another to check if the mouse position is in the bottom half:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noStroke();
  fill(255);
  if (mouseY < height/2) {
    ellipse(200, 100, 150, 150);
  }
  if (mouseY >= height/2) {
    ellipse(200, 300, 150, 150);
  }
}

EXERCISE: Why is does the second if statement use >= as its operator, instead of just >?

This works, but it seems… insufficiently lazy. It turns out that it’s incredibly common to want to perform some behavior when some condition obtains, but some other behavior when that same condition does not obtain. To save you the effort of typing out a second if statement that has the converse of the first statement’s relational operator, Javascript offers an else clause that you can tack on to the end of any if statement.

It looks like this:

if (expr) {
  some-code
}
else {
  some-other-code
}

… where expr is an expression that evaluates to either true or false (e.g., a relational expression or a variable like mouseIsPressed). If that expression evaluates to true, the statements in some-code will be executed. Otherwise, some-other-code will be executed.

So we can re-write the above example somewhat more lazily like so:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noStroke();
  fill(255);
  if (mouseY < height/2) {
    ellipse(200, 100, 150, 150);
  }
  else {
    ellipse(200, 300, 150, 150);
  }
}

Here’s an example that draws a line if the mouse is pressed, and a rectangle otherwise.

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  stroke(255);
  strokeWeight(8);
  noFill();
  ellipse(150, 150, 40, 40);
  ellipse(250, 150, 40, 40);
  if (mouseIsPressed) {
    rect(100, 250, 200, 50);
  }
  else {
    line(100, 275, 300, 275);
  }
}

Odd one out

It’s often the case when using a for loop that you want one particular iteration to have special behavior, different from the rest. An if statement is perfect for this! You can use an if statement inside of a for loop to check to see if the loop variable has a particular value, and engage in different behavior if so:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
  noLoop();
}
function draw() {
  background(50);
  noStroke();
  for (let i = 0; i < 5; i++) {
    if (i == 3) {
      fill(50, 50, 240);
    }
    else {
      fill(255);
    }
    ellipse(100 + (i*50), 200, 45, 45);
  }
}

More than two: else if

We don’t live in an either-or world and so sometimes just having an if and an else isn’t enough. Consider for a moment the if/else demonstration example above, which displays a circle in the top of the sketch if the mouse is in the top half of the sketch, and a circle in the bottom of the sketch if the mouse is in the bottom half of the sketch. Say you wanted to make a similar sketch, but this one divides the screen into thirds instead: if the mouse is in the top third, display a circle there; if it’s in the middle third, display a circle there; if it’s in the bottom third, display a circle there.

In this example, we have three mutually exclusive conditions that we need to test against. The logic looks something like this:

  • Is the mouse in the top third of the screen?
  • If so: draw the circle there.
  • If not, ask: Is the mouse is in the middle third of the screen?
  • If so: draw the circle there.
  • If not: the mouse must be in the bottom third of the screen, so draw the circle there.

The easiest way to capture this logic in Javascript is with the else if clause. Syntactically, the structure looks like this:

if (test) {
    statements
}
else if (test) {
    statements
}
else {
    statements
}

Here’s an implementation of the sketch described above that uses else if:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noStroke();
  fill(255);
  if (mouseY < height * 0.33) {
    ellipse(200, 66, 100, 100);
  }
  else if (mouseY < height * 0.67) {
    ellipse(200, 200, 100, 100);
  }
  else {
    ellipse(200, 333, 100, 100);
  }
}

Any if statement can have any number of associated else if clauses. Even if you have an else if clause, you don’t necessarily need to have an else clause.

Combining relational expressions with && and ||

I explained above that relational expressions with operators like < and > evaluate to special values called booleans. A boolean value is either true or false.

There are two special operators that are used with boolean values: && and ||. The && is called “logical and” and || is called “logical or.” When evaluating expressions with these operators, the resulting value is itself a boolean. You can use these operators to combine relational expressions in order to form more sophisticated relational expressions.

Both the && and || operators work like the other Javascript operators I’ve shown to you so far: they expect an expression on the left and an expression on the right. The && operator evaluates to true only if both the expression on the left and the expression on the right also evaluate to true, and evaluates to false otherwise. The || operator evaluates to true if either expression is true, and to false only if both expressions are false.

You can test out these operators using the same console.log() trick that you’ve used with other operators. So, for example:

console.log((6 > 5) && (7 > 6))

… will display true in the debug area, whereas

console.log((6 > 5) && (10 < 9))

… will display false. Using the || operator:

console.log((6 > 5) || (10 < 9))

… displays true (because only one of the expressions needs to be true for the || operator to succeed), whereas

console.log((4 > 5) || (10 < 9))

… displays false (because both expressions on the left and right of || evaluate to false).

Here’s an example that uses the && operator to check to see if the mouse position is within a particular range. It’s just like the example in the last section, except in this example, a circle is displayed in the center if the mouse position is greater than one third from the top, and less than one third from the bottom. Otherwise, circles are displayed on the top and the bottom.

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noStroke();
  fill(255);
  if ((mouseY > height*0.33) &&
      (mouseY < height*0.67)) {
    ellipse(200, 200, 100, 100);
  }
  else {
    ellipse(200, 66, 100, 100);
    ellipse(200, 333, 100, 100);
  }
}

Here’s an example that draws a circle to the screen if either the mouse is pressed, or the position of the mouse is on the bottom half of the sketch:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  noFill();
  stroke(255);
  strokeWeight(8);
  if (mouseIsPressed || (mouseY > height / 2)) {
    ellipse(200, 200, 300, 300);
  }
}

EXERCISE: Write a sketch that draws a rectangle. If the mouse position is inside the rectangle, draw the rectangle with a blue fill. If the mouse position is outside the rectangle, draw the rectangle with a red fill.

Synthesis: Keeping it in-bounds

One use of if statements and relational expressions is to keep things in bounds. If you have a value that is changing over time, you might want to check to see if that value has exceeded a certain threshold, and, if so, to do something to that value to reset it or change its direction.

The classic example is a bouncing ball: draw an ellipse that moves from left to right. When it reaches the right hand side of the sketch, make it move from right to left. When it reaches the left hand side again, make it move from left to right again.

► run sketch ◼ stop sketch
let xspeed = 4;
let xpos = 0;
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  stroke(255);
  strokeWeight(8);
  ellipse(xpos, 200, 50, 50);
  if (xpos > width) {
    xspeed = -4;
  }
  if (xpos < 0) {
    xspeed = 4;
  }
  xpos += xspeed;
}

You can generalize these by multiplying the xspeed variable by -1, essentially reversing its direction, whenever the X position is “out of bounds”:

► run sketch ◼ stop sketch
let xspeed = 4;
let xpos = 0;
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  stroke(255);
  strokeWeight(8);
  ellipse(xpos, 200, 50, 50);
  if ((xpos > width) || (xpos < 0)) {
    xspeed = xspeed * -1;
  }
  xpos += xspeed;
}

EXERCISE: Modify the sketch above so that it tracks the ellipse’s speed in both the X and Y dimensions. Add a second if statement that keeps the ellipse in-bounds along the Y axis (to complement the existing if statement that does this for the X axis).