Skip to content

Instantly share code, notes, and snippets.

@caseyanderson
Last active October 9, 2022 23:58
Show Gist options
  • Save caseyanderson/b7a6ad65cfd0bf0544688471f07fa218 to your computer and use it in GitHub Desktop.
Save caseyanderson/b7a6ad65cfd0bf0544688471f07fa218 to your computer and use it in GitHub Desktop.

1. Orientation

p5.js is a JavaScript library created to establish computer literacy in visual arts applications. It abstracts the more complex components of JavaScript to provide a friendlier environment for experimentation.

Integrated Development Environment (IDE)

An Integrated Development Environment, or IDE, is a workspace for prototyping and developing code. p5.js has a browser-based IDE which we will use today, but you may want to look into using p5.js with VSCode or another ID. You can the browser-based one here.

Play & Stop

The Play button at the top left corner of the IDE runs or excecutes the code in the sketch window (sketch.js). If a process is running within a loop (i.e. continuously) one can stop it with the Stop button.

sketch.js

The sketch window includes the following code by default:

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

function draw() {
  background(220);
}

Follow Along

  • Add a new line under createCanvas(400, 400)
  • copy and paste the following into line 3: print("hello!");

The code in the sketch window should now look like this now:

function setup() {
  createCanvas(400, 400);
  print("hello!");
}

function draw() {
  background(220);
}

Clicking the play button (i.e. running the sketch) reveals the functionality of the additional line (print()) we just added to the the sketch: it prints the message hello! to the Console, the name for the gray window at the bottom of the browser window.

The Console is an incredibly important tool in the IDE. If you need to double check something it's a good idea to try to print information to the Console with print() (even if it seems redundant).

Reference

Follow Along

  • In the menu (upper left corner of the window near the p5.js logo), click on Help & Feedback
  • Click and select Reference (also directly accessible here)

85% of problems encountered when programming can be resolved by checking the Reference (sometimes referred to as Documentation or Docs), the other 15% comes with experience. BTW, no one expects programmers to memorize everything immediately; instead, programmers are expected to look for resources, like the Reference, that help them figure out how to do something in a particular language.

2. Variables & Datatypes

Variables

A variable is a container for information, or data. In p5js one has to declare a variable's name prior to use. Copy and paste the example below into the IDE and hit the Play button to see "hello!" in the Console.

For Example

function setup() {
  createCanvas(400, 400);
  let data = "hello!";
  print(data);
}

function draw() {
  background(220);
}

Try changing the information (in this case, the word "hello" followed by an exclamation point) contained in data and running the example again.

Strings

A string is a sequence of characters. Strings must be wrapped with balanced quotes. Copy and paste the example below into a new window, delete one of the quotation marks, and try to run the code (this will result in an error)

For example

function setup() {
  createCanvas(400, 400);
  let data = "hello!";
  print(data);
}

function draw() {
  background(220);
}

Numbers

The two most basic numerical datatypes are integers (int) and floating point (float).

Integers

An Integer (int in Processing) is a whole number, or any number without a decimal point. For example: 1 is an integer, as is -1.

Floating Point

A Floating Point (or float in Processing) is any number with a decimal point. 1.2 is a float, as is -2.99.

Random

In a new window type and select random() pull it up in the reference. random(), unsurprisingly, generates random numbers. Try both of the following examples

For Example

function setup() {
  createCanvas(400, 400);
  let randNum = random(50);
  print(randNum);
}

function draw() {
  background(220);
}

Another Example

function setup() {
  createCanvas(400, 400);
  let randNum = random(5, 50);
  print(randNum);
}

function draw() {
  background(220);
}

Note the difference between the two previous examples. Check the reference, what does this indicate about options for using random()?

What if one needed a randomly generated int? Try the following

For Example

function setup() {
  createCanvas(400, 400);
  let randNum = random(5, 50);
  randNum = int(randNum);
  print(randNum);
}

function draw() {
  background(220);
}

Color

color is a datatype in processing for dealing with color values.

Follow along as we explore color in p5js via this nice example.

3. Points, Lines, & 2D Shapes

Point

A point() in p5js is defined as a coordinate on the canvas at the dimension of one pixel. Pull up the reference file for point() and follow along.

Try the following in a new window

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

function draw() {
  background(220);
  point(30, 20);
}

Kind of hard to see, right? One can add color to a point with stroke()

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

function draw() {
  background(220);
  stroke(255,0,0);
  point(30, 20);
}

Note that stroke() uses RGB values here.

Lines

A line is defined as the shortest (or most direct) path between two points (or two point() coordinates). In p5js one can draw a line with line(). Pull up the reference for line().

In order to draw a line one must specify the starting and ending coordinates (or points). One can think of it like this: line(x1, y1, x2,y2). Try the following:

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

function draw() {
  background(220);
  line(30, 20, 85, 75);
}

Super exciting, right? Try adding some color to the line with stroke()

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

function draw() {
  background(220);
  stroke(255, 0, 0);
  line(30, 20, 85, 75);
}

Hooray, a red line()!

Note that line() is a vector, in other words it cannot be filled with a color, so changing the stroke() is the only way to make a different color line.

What if one wanted a thicker line? In order to accomplish this one can use strokeWeight(), which sets the width of the stroke for points, lines, and shapes. strokeWeight() defaults to 1 (i.e. 1 pixel).

For Example

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

function draw() {
  background(220);
  stroke(255, 0, 0);
  strokeWeight(10);
  line(30, 20, 85, 75);
}

2D Shapes

Shapes in Processing are only slightly more complicated than lines and points. One has a variety of options for shapes, but they all share the following characteristics:

  • shapes have a strokeWeight() defining the width of the border
  • the color of this border can be set with stroke()
  • shapes have parameters corresponding to their geometry (width and height, for example) and location (x-coordinate, y-coordinate)
  • shapes can be filled (fill()) with a color

ellipse()

ellipse() draws an oval to the window. Pull up the reference for ellipse() and follow along.

In order to draw an ellipse() one must specify four parameters: x-coordinate, y-coordinate, width, and height. Try the following

For Example

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

function draw() {
  background(220);
  ellipse(20, 30, 40, 50);
}

which results in an oval in the upper lefthand corner of the window.

An ellipse() with equal width and height is a circle

For Example

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

function draw() {
  background(220);
  ellipse(20, 30, 40, 40);
}

One can change the strokeWeight() and stroke() color of an ellipse() as follows

For Example

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

function draw() {
  background(220);
  stroke(255, 0, 0);
  strokeWeight(10);
  ellipse(40, 40, 40, 40);
}

One can change the color inside a shape with fill() as follows

For Example

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

function draw() {
  background(220);
  stroke(255, 0, 0);
  strokeWeight(10);
  fill(0, 255, 0);
  ellipse(40, 40, 40, 40);
}

One can generate an ellipse with a random color stroke filled with blue by running the following

For Example

function setup() {
  createCanvas(400, 400);
  let randomR = int(random(0, 255));
  let randomG = int(random(0, 255));
  let randomB = int(random(0, 255));

  let sColor = color(randomR, randomG, randomB);
  let fColor = color(0, 0, 255);

  strokeWeight(10);
  stroke(sColor);
  fill(fColor);
  ellipse(30, 30, 40, 40);

  print( randomR+", "+randomG+", "+randomB);
}

function draw() {
}

Repeat the above example with each of the following 2D Primitives: quad(), rect(), triangle(), creating a separate file for each shape. Note what you have to do differently for each file and be prepared to turn these three files in at the beginning of next class for credit.

4. basic file structure

The majority of p5js sketches share the same basic structure

For Example

function setup() {

    // setup code goes here, runs once

}

function draw() {

    // main code goes here, runs repeatedly

}

In other words, setup() runs first and only once and then draw() runs repeatedly until the program is stopped.

function setup()

Since setup() only runs once, and first, it is a good part of one's code to perform actions that will not change once draw() is running

For Example

let x = 0;

function setup() {
    createCanvas(200, 200);
    background(0);
    noStroke();
    fill(102);
}

function draw() {
    rect(x, 10, 2, 80);
    x = x + 1;
}

Regardless of what happens in draw(), the following will not change

  • p5js draws to a window whose size is defined by createCanvas(), in this case set to 200 by 200 pixels
  • the background() is black (i.e. 0)
  • whatever is drawn in draw() will have no stroke (i.e. noStroke())
  • whatever is drawn in draw() will have a greyscale color of 102

While there isn't anything wrong with having these lines in draw(), it isn't necessary to set this information repeatedly, which is what would happen if they were in draw(). Part of gaining facility in programming is using structural aspects of the programming language to one's advantage. In this case one can focus their attention on the changing information in draw() and leave all the static (unchanging) lines of code in setup(). This kind of strategic layout leads to cleaner, more readable code.

function draw()

Run the code in the above example to see the lines in draw() in action.

Take a look at the first line: rect(x, 10, 2, 80);

The first line in the draw() block (code between curly brackets is often referred to as a block) draws a rectangle (rect()) to the canvas. There is a variable in the first parameter that we are going to ignore for a moment. In the second parameter (y-coordinate) we have 10. The rect() has a width of 2 pixels and a height of 80 pixels.

Make the following change to the rect() line and run the code again: rect(1, 10, 2, 80);

Now change the first line back to the original and run the code once more: rect(x, 10, 2, 80);

Make a new line under line 12 and add the following: print(x); Now run the code one more time.

Printing the value of x in the draw() block to the console gives a clue as to what is happening in line 12 and why it results in the activity seen in the window.

x = x + 1;

This is known as incrementing and can be translated as follows with every pass through the draw() block: add 1 to whatever x is currently and then update the value stored at x. The first time through draw() x is 0 and, following execution of Line 12, is incremented to 1. The second time through draw() x is 1 and, following execution of Line 12, is incremented to 2, etc.

In other words, because x increases by 1 with every pass through draw() the rectangle appears to expand in width or move (depending on your perspective).

global variables

Line 1 features another idea not yet discussed in this class: global variables. Since x is set to 0 at the top of the file both setup() and draw() have access to it. To prove this delete line 1 and move let x = 0 into setup() and try to run the program, which will "break" our program.

5. Basic Motion

let x = 0;

function setup() {
  createCanvas(200, 200);
  background(0);
  noStroke();
  fill(255, 0, 0);
}

function draw() {
  rect(x, 10, 2, 80);
  x = x + 1;
}

The above example is from the previous lab (although I have changed the fill() to red for fun). Note that this example creates the impression that a skinny rect() gradually expands in width by incrementing the x-position of the rect().

Move the background(0) line from function setup(), as it is above, to the top line of function draw(). It should look like this

For Example

let x = 0;

function setup() {
  createCanvas(200, 200);
  noStroke();
  fill(255, 0, 0);
}

function draw() {
  background(0);
  rect(x, 10, 2, 80);
  x = x + 1;
}

In the version at the top of the page, where background(0) is in function setup(), the background color is drawn once (and first) only, which is why it appears that the rect() is growing or expanding. In the second example background(0) happens with every pass through function draw(). When background() is in function draw() it clears the display window at the beginning of each frame before drawing a new rect() 1 pixel to the right. This creates the impression that the rect() is moving from left to right.

frameRate()

There are a variety of ways to make something move faster in p5js, but the simplest is by increasing the frameRate(). Pull up frameRate() in the reference and follow along.

frameRate() specifies the number of frames to be displayed per second. The default frameRate() is 60 fps (frames per second). Try the following

For Example

let x = 0;

function setup() {
  frameRate(30);
  createCanvas(200, 200);
  noStroke();
  fill(255, 0, 0);
}

function draw() {
  background(0);
  rect(x, 10, 2, 80);
  x = x + 1;
}

Comment out the frameRate() line in the example above and compare to the default. frameRate(30) is half as fast as the default so, unsurprisingly, the rect() takes twice as long to go from the left edge of the screen to the right.

Changing Rate of Motion Mathematically

frameRate() is fine, but a better way to change rate of motion would be to do so mathematically. Try the following

For Example

// twice as fast

let x = 0;

function setup() {
  createCanvas(200, 200);
  noStroke();
  fill(255, 0, 0);
}

function draw() {
  background(0);
  rect(x, 10, 2, 80);
  x = x + 2;
  print(x);
}

For Example

// three times as fast

let x = 0;

function setup() {
  createCanvas(200, 200);
  noStroke();
  fill(255, 0, 0);
}

function draw() {
  background(0);
  rect(x, 10, 2, 80);
  x = x + 3;
  print(x);
}

In both of the previous examples the only difference is the number that is added to x.

Addition is the simplest way to animate behavior, but one can also use multiplication to create a sense of motion. Try the following

For Example

// exponentially increase rate of motion

let x = 1;

function setup() {
  createCanvas(200, 200);
  noStroke();
  fill(255, 0, 0);
}

function draw() {
  background(0);
  rect(x, 10, 2, 80);
  x = x * 2;
  print(x);
}

In all previous examples x was initialized with a value of 0. Here x has to be initialized with a value of 1. Change it to 0 and note what happens. Try using print() on x in this version to see why one cannot initialize x with a value of 0 when exponentially increasing its position.

Incrementing Multiple Parameters

By adding another variable to increment, and using it in another parameter, one can get more complex results. Try the following

For Example

// incrementing x and y positions

let x = 0;
let y = 0;

function setup() {
  createCanvas(200, 200);
  noStroke();
  fill(255, 0, 0);
}

function draw() {
  background(0);
  rect(x, y, 2, 80);
  x = x + 1;
  y = y + 1;
}

Similarly, one can add a third variable to increment to create a sense of expansion in the rect().

For Example

// incrementing x and y positions and width

let x = 0;
let y = 0;
let z = 0;

function setup() {
  createCanvas(200, 200);
  noStroke();
  fill(255, 0, 0);
}

function draw() {
  background(0);
  rect(x, y, z, 80);
  x = x + 1;
  y = y + 1;
  z = z + 1;
}

6. Control Structures

Boolean Logic

Boolean Logic is a branch of mathematics where values can only be True or False. It is also an incredibly important component of programming.

if statements

The most common boolean structure is an if statement. Try the following, which you will recognize from previous labs

For Example

let x = 0;

function setup() {
  createCanvas(200, 200);
  noStroke();
  fill(255, 0, 0);
}

function draw() {
  background(0);
  rect(x, 10, 2, 80);
  x = x + 1;
  if ( x > width ){
    x = 0;
  }
}

Note the additional if statement in function draw(). One way to read this is as follows: if x is greater than the width of the canvas then reset x to 0. This if statement creates the effect that the rect() loops back to the left side of the canvas when it reaches the right side.

All if statements share the same basic structure:

if (test) {
  action
}

which can be read as: if test is True then perform action.

if / else

An if statement alone only tests for one condition and, if it is True, performs an action. However, with the addition of else one can specify two different possible actions. Abstractly, the structure looks like this:

if (test) {
  action1
} else {
  action2
}

Which can be read as: if test is the case (True) then perform action 1, otherwise (else) perform action 2.

for loops

for loops use boolean logic to perform the same action (or function) for every item in a collection. Before going further with for loops try the following

For Example

function setup(){
  background(255);
  stroke(255, 0, 0);
}

function draw(){
  line(30, 40, 80, 40);
  line(30, 45, 80, 45);
  line(30, 50, 80, 50);
  line(30, 55, 80, 55);
  line(30, 60, 80, 60);
  line(30, 65, 80, 65);
  line(30, 70, 80, 70);
  line(30, 75, 80, 75);
}

In the above example 8 lines are drawn to the window. As you can see, there is a lot of repetition. Take a look at the code and see if you can find a pattern.

Another way to read the code in function draw() above would be as follows:

  1. draw a line starting from point 30, 40 and ending at 80, 40
  2. draw a line starting from point 30, 45 and ending at 80, 45
  3. draw a line starting from point 30, 50 and ending at 80, 50
  4. draw a line starting from point 30, 55 and ending at 80, 55
  5. draw a line starting from point 30, 60 and ending at 80, 60
  6. draw a line starting from point 30, 65 and ending at 80, 65
  7. draw a line starting from point 30, 70 and ending at 80, 70
  8. draw a line starting from point 30, 75 and ending at 80, 75

By using a for loop one can avoid duplicating the same line 8 times, as we do above, and instead specify a more general structure to represent this task. Try the following, which results in the exact same output but takes advantage of the for loop structure (and requires way less repetitive typing!)

For Example

function setup(){
  background(255);
  stroke(255, 0, 0);
}

function draw(){
  for (let i = 40; i < 80; i = i+5) {
    line(30, i, 80, i);
  }
}

for loop structure

for (init; test; update) {
  action
}

Which can be read, generally, as follows:

  1. The init statement is run
  2. The test is evaluated to be True or False
  3. If the test is True, jump to step 4. If the test is False, jump to step 6
  4. Run the statements (actions) within the block
  5. Run the update statement and jump to step 2
  6. Exit the loop

7. Mouse & Keyboard Input

Mouse Input

We have two built-in objects in p5js to access the current location of the mouse cursor in the canvas: mouseX and mouseY. Try the following

For Example

function draw() {
  frameRate(12);
  print(mouseX + " : " + mouseY);
}

Note that the print() statement only works within the p5js canvas because p5js only registers the mouse location there. If one wanted to use a larger area of the screen for mouse input one would need to make a larger canvas. Try the following

For Example

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

function draw() {
  frameRate(12);
  print(mouseX + " : " + mouseY);
}

By plugging mouseX and mouseY into the first two parameters of an ellipse(), for example, we can use our mouse to move an ellipse() around in the canvas. Try the following

For Example

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

function draw() {
  background(255);
  fill(255, 0, 0);
  ellipse(mouseX, mouseY, 33, 33);
  print(mouseX + " : " + mouseY);
}

An alternate way to approach this would be to store the mouseX and mouseY coordinates in variables, which would enable us to reuse the coordinates in different ways throughout our code. Try the following

For Example

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

function draw() {
  let mX = mouseX;
  let my = mouseY;
  background(255);
  fill(255, 0, 0);
  ellipse(mX, my, mX * 2, my * 2);
  print(mX + " : " + my);
}

The benefit here is that we can wildly experiment without having to retype the same thing (mouseX and mouseY) over and over again.

For Example

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

function draw() {
  let mX = mouseX;
  let my = mouseY;
  background(255);
  fill(255, mX, my);
  ellipse(mX, my, (mX+1) / 2, (my+1) / 2);
  print(mX + " : " + my);
}

One can also access mouse buttons to, as seen below, change what color a rect() is filled with. Try the following

For Example

function draw() {
  mousePressed();
  rect(25, 25, 50, 50);
}

function mousePressed() {
  if (mouseButton == LEFT) {
    print("LEFT MOUSE BUTTON PRESSED");
    fill(0);
  } else if (mouseButton == RIGHT) {
    print("RIGHT MOUSE BUTTON PRESSED");
    fill(255);
  } else {
    print("NO BUTTON");
    fill(255, 0, 0);
  }
}

The above example also features an important, and new, component of Boolean Logic. In the past we have discussed Boolean tests with two components: if and else. I often read these structures like this: if test is the case perform action 1, otherwise perform action 2.

If one needs more than two options one can add an arbitrary number of else if statements after the initial if. One way to read the above example is, which features an if followed by an else if and then an else is: if the left mouse button is pressed fill the rectangle with 0, if the right mouse button is pressed fill the rectangle with 255, otherwise (if neither the left nor right buttons are pressed) fill the rectangle with 126. In this case one can think of the else action as the default behavior (i.e. most of the time the rect() will be filled with red).

One interesting phenomena here is that, after a mouse button press, we need to move our cursor outside of the canvas in order to return the rectangle to its red state. We could add a bunch of features, like a timer, to afunction this phenomena but that is a topic for another time.

There are a variety of other mouse actions that one can access in p5js, so it is worth heading to the reference to see what else is possible. I often map parameters of objects I am unfamiliar with to the mouse because it is a fast way to "sketch," or quickly experiment and get some usable feedback. If you print() the mouse coordinate values to the console (as we do in the examples above) you can visually find something you like and then check the data to see what you are doing, which is a fun way to figure out what numbers you need to use to get the effect you are shooting for.

Keyboard Input

In a way mouse input is simpler than keyboard input, as there are a lot more keys on a keyboard than buttons on a mouse.

Let's say I want to change the color of a rect() whenever a certain key is pressed. Try the following

For Example

function draw() {
  if (keyIsPressed) {
    if (key == 'b' || key == 'B') {
      fill(0);
    }
  } else {
    fill(255);
  }
  rect(25, 25, 50, 50);
}

A few things worth pointing out here:

  • The canvas has to be in "focus" in order for this example to work, so if one clicks on a different application, for example, p5js will stop listening for key presses. Even if one clicks on the p5js IDE the canvas will stop responding to keyboard presses (which makes sense in a way)
  • Note that we have nested if statements here: first we test to see if a key has been pressed (with keyIsPressed) and then we test to see whether that key is b or B. One can read this as: if a key has been and pressed and if that key is b or B then fill the rect() with 0, otherwise fill it with 255
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment