Interaction
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:
function setup() { createCanvas(200, 200); noLoop(); } function draw() { background(0); fill(255); ellipse(width/2, height/2, width/3, height/3); }
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:
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:
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:
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:
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:
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.
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:
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:
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:
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:
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:
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:
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.
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:
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
:
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.
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:
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.
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”:
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 existingif
statement that does this for the X axis).