Welcome to Week 2! Last week, we drew static shapes on our canvas - they appeared exactly where we told them to appear, with the exact colours and sizes we specified. But what if we want things to change? What if we want our shapes to respond to us, to move, to live?
This week, we’re going to explore one of the most fundamental concepts in programming: variables. But we’re not just going to learn how to use them - we’re going to unpack what they are, what they assume, and what they encode about how we think about memory, change, and categories.
Discussion
Before we write any code, let’s sit with these questions:
When we say something is “variable”, what are we really saying? Variable within what boundaries?
Think about some of categories in our lives that are treated as fixed versus those that are allowed to change. Who decides which is which?
In computing, we talk about “memory” - as if the computer remembers like we do. But is it the same? What does it mean for a machine to “remember” something?
What if we wanted to change the size of this rectangle? We’d have to manually change those numbers - the 100, 100. But what if we could give that size a name? What if we could say “let the size be 100” and then use “size” whenever we need it?
That’s what variables do. But let’s be precise about what’s happening here. We’re creating an abstraction - a named container that holds a value. And in doing so, we’re making decisions about:
What deserves a name
What is allowed to change
What must be explicitly stated versus what remains implicit
In JavaScript (and therefore in p5.js), we have two main ways to create variables: let and const.
let - for things we permit to change
1
let boxSize = 100;
const - for things we declare unchangeable
1
const CANVAS_SIZE = 400;
Notice the language here. let - as in “let it be”. const - short for “constant”, meaning unchanging. But who decides what’s constant? In our code, we do. But in the systems we build, those decisions have consequences.
Let’s rewrite our rectangle example:
1
functionsetup() {
2
const CANVAS_SIZE = 400;
3
let boxSize = 100;
4
5
createCanvas(CANVAS_SIZE,CANVAS_SIZE);
6
background(220);
7
rect(200,200,boxSize,boxSize);
8
}
Now boxSize is variable - it can change. But CANVAS_SIZE is constant - we’ve declared it immutable. These are our rules, our little universe.
When we write let boxSize = 100, something interesting happens in the computer’s memory. The computer allocates a small piece of memory - think of it as a tiny box - and puts the number 100 in it. Then it creates a label, “boxSize”, that points to that location.
But here’s what’s fascinating and troubling: the computer doesn’t actually store “100” the way we think of it. It stores a binary representation - 01100100 - a series of electrical charges that are either on or off. Everything is reduced to 1s and 0s. Every nuance, every variation, every shade of gray gets converted into binary. On or off. True or false.
This is what Pierrot, Snelting, and Roscam Abbing talk about when they discuss the “universal” in computing. There’s an assumption that everything can be reduced, categorized, made discrete.
JavaScript (like most programming languages) classifies data into types. Three of the most basic types are:
number - for numeric values
1
let age = 25;
2
let temperature = -3.5;
3
let pixels = 1920;
string - for text
1
let name = "Ada";
2
let feeling = "uncertain";
3
let color = "#FF0000";
Strings must always be contained within a set of quotation marks - either single or double (but not both). And all the text within those quotes is considered as the string. For example, "Ada" is a string, "uncertain" is a string, "#FF0000" is a string.
"My name is Ada' is not a string (mismatched quotes);
"My name is Ada" is a string.
boolean - for true/false values
1
let isPressed = false;
2
let isVisible = true;
3
let isComplete = false;
We can check what type something is using typeof:
1
functionsetup() {
2
let size = 100;
3
let message = "hello";
4
let isActive = true;
5
6
console.log(typeofsize); // "number"
7
console.log(typeofmessage); // "string"
8
console.log(typeofisActive); // "boolean"
9
}
But here’s a question: why these categories? Why not others? What worldview is encoded in deciding that these are the “primitive” types?
Variables wouldn’t be variables if they couldn’t change. Let’s explore how we modify them - how we mutate them.
1
let x = 100; // x is now 100
2
x=200; // x is now 200
3
x=x+50; // x is now 250
That last line - x = x + 50 - is curious. In mathematics, this would be nonsense. But in programming, the = isn’t about equality. It’s about assignment. It means “take the value on the right, and store it in the variable on the left.”
This is reassignment. This is mutation. We’re changing what x means, what it holds.
Because updating variables is so common, JavaScript gives us shortcuts:
1
let x = 100;
2
3
// Addition
4
x=x+10; // the explicit way
5
x+=10; // the shorthand
6
7
// Subtraction
8
x=x-5;
9
x-=5;
10
11
// Increment by 1
12
x=x+1;
13
x+=1;
14
x++; // the shortest way
15
16
// Decrement by 1
17
x=x-1;
18
x-=1;
19
x--;
These operators - +=, -=, ++, -- - are about accumulation and depletion. They’re about counting, tracking, measuring. They assume that change is quantifiable, that it happens in discrete steps.
When we create a new sketch on p5.js, it automatically sets up the boilerplate code for us - with a setup function and a draw function. Last week (and until now), we have only been using setup(), now let’s add back the draw() function to our code and understand how it works.
If you have worked with flipbooks or any other digital animation tools, you might have worked with frames. Each frame is an image that is displayed for a certain amount of time, and then the next frame is displayed, and so on. This creates the illusion of motion.
It’s the same principle in p5.js too. We create a series of frames that are displayed one after another in a sequence, and this create the motion.
That is where the draw() function comes in. The draw() function is executed almost 60 times a second, continuously. And it will keep on doing that until we stop the sketch.
But wait, where does the 60 come from?
The 60 comes from the refresh rate of the monitor. Most monitors refresh at 60 frames per second (fps), which means they display a new frame every 1/60th of a second. This is why the draw() function tries to run it 60 times per second. In other words, we are running our sketches at 60fps.
Which also means that there should be some way to adjust that number, right? p5 also makes it easier for us to do that with the frameRate(). Instead of running a sketch at 60fps, if we want to run it in fewer frames per second, we can specify the number.
1
let x = 0;
2
3
functionsetup() {
4
createCanvas(400,400);
5
background(155);
6
}
7
8
functiondraw() {
9
circle(x,height/2,20);
10
x++;
11
frameRate(10);
12
}
Now the draw() function in our sketch will only run 10 times per second, instead of the default 60.
Now things get interesting. p5.js also tracks the mouse cursor and gives us variables for its position: mouseX and mouseY.
1
functionsetup() {
2
createCanvas(400,400);
3
}
4
5
functiondraw() {
6
background(220);
7
circle(mouseX,mouseY,50);
8
}
Run this. Move your mouse. The circle follows you.
What’s happening? Every time draw() runs (60 times per second), p5.js updates mouseX and mouseY with the current cursor position. The circle is drawn at that position. The background is redrawn, erasing the previous frame.
Think about this for a moment. Your body’s movement - the physical gesture of moving your hand - is being continuously sampled, quantized into x and y coordinates, and translated into graphics. Your analog motion becomes digital data. What’s lost in that translation?
There are also pmouseX and pmouseY - the previous mouse position from the last frame.
1
functionsetup() {
2
createCanvas(400,400);
3
background(220);
4
}
5
6
functiondraw() {
7
// Draw a line from previous position to current position
8
line(pmouseX,pmouseY,mouseX,mouseY);
9
}
Now we’re drawing, creating trails. The computer remembers where we were and connects it to where we are. This is memory made visible.
Let’s combine what we’ve learned. Variables can control any property of a shape - position, size, color.
1
let circleSize = 20;
2
let bgColor = 220;
3
4
functionsetup() {
5
createCanvas(400,400);
6
}
7
8
functiondraw() {
9
background(bgColor);
10
11
// Size follows mouseX
12
circleSize=mouseX/10;
13
circle(mouseX,mouseY,circleSize);
14
}
Here, the circle’s size is determined by the mouse’s x position. Move left, it shrinks. Move right, it grows. We’ve created a relationship between input and output, between gesture and form.
Let’s go further:
1
let circleSize = 20;
2
let red = 255;
3
let blue = 0;
4
5
functionsetup() {
6
createCanvas(400,400);
7
}
8
9
functiondraw() {
10
background(220);
11
12
// Map mouse position to size
13
circleSize=mouseX/10;
14
15
// Map mouse position to color
16
red=mouseX/400*255;
17
blue=mouseY/400*255;
18
19
fill(red,0,blue);
20
circle(mouseX,mouseY,circleSize);
21
}
Now both size and color respond to our movement. The interface becomes an instrument.
So far, everything we’ve done happens all the time. But what if we want something to happen only sometimes? What if we want to make decisions?
This is where conditionals come in. Specifically, the if statement.
In simple terms, if something is something, do something.
For example, if the mouse’s x position is greater than 200, set the background to red. That also means that the background will be red only and only when the mouse’s x position is greater than 200. So anything that’s contained within the curly braces will only run when the condition is true.
1
if (mouseX>200) {
2
background(255, 0, 0); // red background
3
}
Let’s see it in action:
1
functionsetup() {
2
createCanvas(400,400);
3
}
4
5
functiondraw() {
6
background(220);
7
8
if (mouseX>200) {
9
fill(255,0,0); // red
10
} else {
11
fill(0,0,255); // blue
12
}
13
14
circle(mouseX,mouseY,50);
15
}
Now the circle changes color depending on which half of the screen the mouse is on. We’ve divided our space into zones, into categories.
We can make more complex decisions:
1
functionsetup() {
2
createCanvas(400,400);
3
}
4
5
functiondraw() {
6
background(220);
7
8
if (mouseX<133) {
9
fill(255,0,0); // red
10
} elseif (mouseX<266) {
11
fill(0,255,0); // green
12
} else {
13
fill(0,0,255); // blue
14
}
15
16
circle(mouseX,mouseY,50);
17
}
Three zones. Three categories. Three colors. We’ve created a system of classification based on position.
Let’s create something that combines everything we’ve learned:
1
let circleX = 200;
2
let circleY = 200;
3
4
functionsetup() {
5
createCanvas(400,400);
6
}
7
8
functiondraw() {
9
background(220,220,220,10); // slightly transparent background for trails
10
11
// Move circle towards mouse
12
if (mouseX>circleX) {
13
circleX+=2;
14
} else {
15
circleX-=2;
16
}
17
18
if (mouseY>circleY) {
19
circleY+=2;
20
} else {
21
circleY-=2;
22
}
23
24
// Color based on distance from mouse
25
let distance = dist(circleX,circleY,mouseX,mouseY);
26
let colorValue = distance / 2;
27
28
fill(colorValue,100,255-colorValue);
29
circle(circleX,circleY,50);
30
}
Run this. The circle chases your mouse, but never quite catches it. It leaves trails. Its color changes based on distance. We’ve created a system with its own logic, its own behavior.
But notice what we’ve encoded here: pursuit, distance, color as a function of proximity. These are our decisions, our values made algorithmic.
What does it mean that everything in computing must be given a type? What gets lost when we categorize things into number, string, boolean?
When you use conditionals, you’re creating boundaries and categories. What are the politics of those boundaries? Who or what gets included or excluded?
The mouse variables (mouseX, mouseY) translate your physical movement into data. What assumptions does this translation make about bodies, about movement, about interaction?
Variables can be changed (let) or fixed (const). In the systems you’re building, what do you choose to make mutable and what do you make constant? Why?
Building on last week’s static self-portrait, create a responsive self-portrait that changes based on mouse interaction. Your portrait should use variables to control at least three different properties (position, size, color, etc.) and use conditionals to create at least two different “states” or behaviors.
Consider: what does it mean for a self-portrait to be variable? What aspects of identity are you choosing to make responsive, and what does that say about how you understand yourself in relation to others?
The technical requirements:
Use let to declare variables
Use mouse variables (mouseX, mouseY, or/and pmouseX, pmouseY) to control visual properties
Include at least 2 conditional statements that change behavior based on mouse position
Use at least one operator (+=, -=, ++, or --) to create change over time