Variables and Interactivity
Variables and Interactivity
In this step-by-step tutorial, you’ll build an interactive face drawing using p5.js.
You’ll learn how to use variables to organize your code, and mouseX/mouseY to add interactivity.
Use the ◀ ▶ buttons or arrow keys to step through the stages. Drag any number in the code to change it and see the result update live.
Here is a basic face.
Some numbers — 200, 80, etc. — are repeated several times.
function setup() {
createCanvas(windowWidth, windowHeight);
background("pink");
}
function draw() {
fill("white");
circle(85, 200, 80);
circle(185, 200, 80);
fill("blue");
circle(85, 200, 80-30);
circle(185, 200, 80-30);
fill("black");
circle(85, 200, 15);
circle(185, 200, 30);
fill("purple");
rect(55, 310, 180, 20);
circle(55, 305, 60);
circle(55+170, 305, 60);
} Replace 200 by a variable eyeY that has the value 200.
This has the same behavior. It is more self-documenting (you can read line 10 and 11, and see what the value means). Also it is more flexible. You can move the eyes higher by editing line 7, instead of finding all the 200’s.
function setup() {
createCanvas(windowWidth, windowHeight);
background("pink");
}
function draw() {
let eyeY = 200;
fill("white");
circle(85, eyeY, 80);
circle(185, eyeY, 80);
fill("blue");
circle(85, eyeY, 80-30);
circle(185, eyeY, 80-30);
fill("black");
circle(85, eyeY, 15);
circle(185, eyeY, 30);
fill("purple");
rect(55, 310, 180, 20);
circle(55, 305, 60);
circle(55+170, 305, 60);
} This step replaces 310 by a variable mouthY.
Note that the circles are offset from mouthY. They are a little higher, so an expression mouthY - 5 is used instead of mouthY.
function setup() {
createCanvas(windowWidth, windowHeight);
background("pink");
}
function draw() {
let eyeY = 200;
let mouthY = 310;
fill("white");
circle(85, eyeY, 80);
circle(185, eyeY, 80);
fill("blue");
circle(85, eyeY, 80-30);
circle(185, eyeY, 80-30);
fill("black");
circle(85, eyeY, 15);
circle(185, eyeY, 30);
fill("purple");
rect(55, mouthY, 180, 20);
circle(55, mouthY - 5, 60);
circle(55+170, mouthY - 5, 60);
} eyeY and mouthY are variables that we defined ourselves. mouseX and mouseY are variables that p5.js defines, and that we can use. Here, the x position of the right eye is mouseX. This causes it to follow the mouse.
This leaves a trail of eyes. We’ll fix that in the next step.
function setup() {
createCanvas(windowWidth, windowHeight);
background("pink");
}
function draw() {
let eyeY = 200;
let mouthY = 310;
fill("white");
circle(85, eyeY, 80);
circle(mouseX, eyeY, 80);
fill("blue");
circle(85, eyeY, 80-30);
circle(185, eyeY, 80-30);
fill("black");
circle(85, eyeY, 15);
circle(185, eyeY, 30);
fill("purple");
rect(55, mouthY, 180, 20);
circle(55, mouthY - 5, 60);
circle(55+170, mouthY - 5, 60);
} Move the call to background() from setup() to draw(), so that the screen clears each time.
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background("pink");
let eyeY = 200;
let mouthY = 310;
fill("white");
circle(85, eyeY, 80);
circle(mouseX, eyeY, 80);
fill("blue");
circle(85, eyeY, 80-30);
circle(mouseX, eyeY, 80-30);
fill("black");
circle(85, eyeY, 15);
circle(mouseX, eyeY, 30);
fill("purple");
rect(55, mouthY, 180, 20);
circle(55, mouthY - 5, 60);
circle(55+170, mouthY - 5, 60);
} I don’t really want to move the whole eye. This version moves just the center.
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background("pink");
let eyeY = 200;
let mouthY = 310;
fill("white");
circle(85, eyeY, 80);
circle(185, eyeY, 80);
fill("blue");
circle(85, eyeY, 80-30);
circle(mouseX, eyeY, 80-30);
fill("black");
circle(85, eyeY, 15);
circle(mouseX, eyeY, 30);
fill("purple");
rect(55, mouthY, 180, 20);
circle(55, mouthY - 5, 60);
circle(55+170, mouthY - 5, 60);
} The new code on lines 17–20 prevents the pupil and iris of the right eye from moving to the right of the sclera (the white part of the eye).
Try moving the mouse around, and see how this differs from the previous step.
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background("pink");
let eyeY = 200;
let mouthY = 310;
fill("white");
circle(85, eyeY, 80);
circle(185, eyeY, 80);
fill("blue");
circle(85, eyeY, 80-30);
let rightPupilX = mouseX;
if (rightPupilX > 185 + 15) {
rightPupilX = 185 + 15;
}
circle(rightPupilX, eyeY, 80-30);
fill("black");
circle(85, eyeY, 15);
circle(rightPupilX, eyeY, 30);
fill("purple");
rect(55, mouthY, 180, 20);
circle(55, mouthY - 5, 60);
circle(55+170, mouthY - 5, 60);
} This clamps the right iris so that it stays entirely within the eye.
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background("pink");
let eyeY = 200;
let mouthY = 310;
fill("white");
circle(85, eyeY, 80);
circle(185, eyeY, 80);
fill("blue");
circle(85, eyeY, 80-30);
let rightPupilX = mouseX;
if (rightPupilX > 185 + 15) {
rightPupilX = 185 + 15;
}
if (rightPupilX < 185 - 15) {
rightPupilX = 185 - 15;
}
circle(rightPupilX, eyeY, 80-30);
fill("black");
circle(85, eyeY, 15);
circle(rightPupilX, eyeY, 30);
fill("purple");
rect(55, mouthY, 180, 20);
circle(55, mouthY - 5, 60);
circle(55+170, mouthY - 5, 60);
} This step copies and modifies the code that was used to track the mouse x position, and uses it to track the y position as well.
You may notice that the iris no longer stays entirely within the white circle. This is because clamping x within a range, and separately clamping y within a range, has the effect of keeping the iris within a rectangle, not a circle.
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background("pink");
let eyeY = 200;
let mouthY = 310;
fill("white");
circle(85, eyeY, 80);
circle(185, eyeY, 80);
fill("blue");
circle(85, eyeY, 80-30);
let rightPupilX = mouseX;
if (rightPupilX > 185 + 15) {
rightPupilX = 185 + 15;
}
if (rightPupilX < 185 - 15) {
rightPupilX = 185 - 15;
}
let rightPupilY = mouseY;
if (rightPupilY > eyeY + 15) {
rightPupilY = eyeY + 15;
}
if (rightPupilY < eyeY - 15) {
rightPupilY = eyeY - 15;
}
circle(rightPupilX, rightPupilY, 80-30);
fill("black");
circle(85, eyeY, 15);
circle(rightPupilX, rightPupilY, 30);
fill("purple");
rect(55, mouthY, 180, 20);
circle(55, mouthY - 5, 60);
circle(55+170, mouthY - 5, 60);
} Lines 37–41 draw a rectangle around the eye. (This is its “bounding box.”) This makes it easier to see what is going on.
This is a useful technique: draw something on the screen that is not part of the final version of your sketch, in order to help you understand or debug what is going on.
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background("pink");
let eyeY = 200;
let mouthY = 310;
fill("white");
circle(85, eyeY, 80);
circle(185, eyeY, 80);
fill("blue");
circle(85, eyeY, 80-30);
let rightPupilX = mouseX;
if (rightPupilX > 185 + 15) {
rightPupilX = 185 + 15;
}
if (rightPupilX < 185 - 15) {
rightPupilX = 185 - 15;
}
let rightPupilY = mouseY;
if (rightPupilY > eyeY + 15) {
rightPupilY = eyeY + 15;
}
if (rightPupilY < eyeY - 15) {
rightPupilY = eyeY - 15;
}
circle(rightPupilX, rightPupilY, 80-30);
fill("black");
circle(85, eyeY, 15);
circle(rightPupilX, rightPupilY, 30);
noFill();
stroke('lightblue');
strokeWeight(4);
rect(185-40-2, eyeY-40-2, 80+4, 80+4);
noStroke();
fill("purple");
rect(55, mouthY, 180, 20);
circle(55, mouthY - 5, 60);
circle(55+170, mouthY - 5, 60);
} We can “comment out” the code in order to keep it from running, while leaving it in position so that we can uncomment it later.
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background("pink");
let eyeY = 200;
let mouthY = 310;
fill("white");
circle(85, eyeY, 80);
circle(185, eyeY, 80);
fill("blue");
circle(85, eyeY, 80-30);
let rightPupilX = mouseX;
if (rightPupilX > 185 + 15) {
rightPupilX = 185 + 15;
}
if (rightPupilX < 185 - 15) {
rightPupilX = 185 - 15;
}
let rightPupilY = mouseY;
if (rightPupilY > eyeY + 15) {
rightPupilY = eyeY + 15;
}
if (rightPupilY < eyeY - 15) {
rightPupilY = eyeY - 15;
}
circle(rightPupilX, rightPupilY, 80-30);
fill("black");
circle(85, eyeY, 15);
circle(rightPupilX, rightPupilY, 30);
// noFill();
// stroke('lightblue');
// strokeWeight(4);
// rect(185-40-2, eyeY-40-2, 80+4, 80+4);
// noStroke();
fill("purple");
rect(55, mouthY, 180, 20);
circle(55, mouthY - 5, 60);
circle(55+170, mouthY - 5, 60);
} Try it on OpenProcessing: Interactive Face Sketch