There are other canvas plus libraries out there that you might prefer over p5js. The fundamentals you will see are more or less the same, but different libraries have different features. p5js focuses on ease of use and leverages the large Processing community.
Unlike Processing, which can do 2D and 3D both fairly well, p5js is still very much a 2D library. There is work underway to improve WebGL support and make it 3D as well but there's no public release of this work yet at the time of this writing.
I recommend that you work in Firefox, it has the best developer tools and it is the most standards-compliant browser. You can access the editor here, create an account for yourself so that you can save your experiments and come back to them later. (in class we have experienced some issues with the p5js editor in Firefox, copy and paste not working for example, perhaps switch back to Chrome if you encounter this problem?)
These cheatsheets will help you follow:
p5js provides you with two major hooks to initialize and to paint in your sketch. When your sketch is loaded p5js will call the function called setup()
if it exists in your sketch and it will execute it only once. This is a good place to create your canvas, initialize any objects or arrays that your sketch might need, basically any bootstrapping required by your sketch. If you draw anything here it will also be drawn, but it will only be drawn one, so this is not a good place to do animation.
p5js will then try to execute a function called draw()
in your code, if it finds it, p5js will go into an infinite drawing loop executing this function at every frame. This is the right place to put your drawing and animation code.
For example this little program hooks onto both of p5js' default functions but doesn't do anything in either, nothing is shown as a result.
Writing code is fairly tricky when you are not used to it. Many things can go wrong and throw off your browser. Do not fear, things get better with practice and generally you can't break anything when you code in Javascript, so you have the license to experiment.
Every program must abide by strict syntax and grammar rules, a simple misplaced comma can cause your sketch to display an error message. Error messages are there to tell you what is wrong with your code. Read them carefully and try to decipher their meanings.
The important bit in this error message is the bit that says cruateCanvas is not defined
, that means that we are trying to use something in our program called cruateCanvas
that apparently doesn't exist.
Panic doesn't solve programming errors. Deal with errors with serenity. Time an practice will make interpreting these seemingly arcane messages second nature.
Before we draw anything in p5js, we need to create a canvas with a specific dimension. In this example we will create a canvas that is 300 pixels wide by 100 pixels high, and then give it a background color of pink so that we can see it clearly.
Let's now draw a single point in space, at coordinates (0,0).
Do it yourself: try placing the dot in the center of the screen, then one third to the left and lastly one forth from the right edge. To calculate the positions you can use the width
and height
variables in p5js, which contain the dimensions of the canvas.
That dot is kind of hard to see isn't it? Let's draw something else, something that is a little less challenging to actually visualize. Let's draw a circle.
The ellipse
function takes 4 parameters, the first two determine the position of its center and the other two determine the width
and height
of the ellipse respectively, when these two values are the same the resulting shape is a perfect circle. So the circle is basically a special case of the ellipse
primitive. Try and change the values and see how this code behaves.
Other primitives in p5js include the triangle()
, rect()
, quad()
, ellipse()
, and arc()
. The circle and the square are special cases of ellipse()
and rect()
respectively. Let's play around with some of these.
As you can see when we try to draw things relative to each other, the calculations to place things on the canvas can become quite overwhelming and difficult to track, in this case we are using a variable named xpos
to keep track of the x
position of our shapes, but then we have to calculate each coordinate of each shape. This will quickly become quite a hassle to maintain and our code will become littered with magical numbers that we will not be able to read a few weeks from now. There must be a better way!
A point is a one dimensional primitive, so we only need to pass the X
and Y
position of its location to draw it. But a line can be two dimensional, to define a line we need to have two points. Therefore we need two coordinates.
Do it yourself: try the same thing with the line that you tried with the dots, in the X
axis and then try again for the Y
axis.
Using simple lines and a coloring trick we can draw a button, or let's better call it a shape that our eye will read as a button.
This simple coloring technique is fundamental to how we perceive UI widgets, most early browser buttons were draw using variations of this technique. It is so fundamental that artist Jan Robert Leegte made an artwork about it in the first example that I know of UI-inspired minimalism.
Or if you prefer an old material design approach to drawing a button.
p5js give's us an easy way to get the current position of our mouse. The X position of our mouse is stored in the mouseX
variable, and mouseY
constains the Y. With this knowledge, let's implement a simple mouse over for our super-primitive button.
Observe how the visual effect in this interaction is entirely determined by a change of color. In this second example I decided not to use black and white to draw the edges of the button and instead used different shades in the pink tint, from a bright pink to a dark, almost brownish tone. What effect does this have in how you perceive the button? How would these two buttons feel to the touch if they were objects and you could run your finger through them?
Let's use a for loop
to draw circles in different arrangements.
Let's at the moment we use the for loop to draw multiple circles and change their x
coordinate. Let's try and change more than one parameter, let's change colors too.
The formula to arrange items in a circle uses trigonometric functions sin()
and cos()
. To arrange things in a circle we need to know the radius
of that circle too. The sinus
gives us the x
position and the cosinus
gives us the y
position.
x = center.x + radius * sin(angle);
y = center.y + radius * cos(angle);
To better understand the relationship between trigonometric functions and the shape of a circle this Khan academy video does a pretty good job at showing it.
Reuben Margolin creates kinetic installations that represent waves all based on this principle.
On minute 6:30 you can see a prototype of the principle at work.
The push()
and pop()
functions of p5js can help us in handling our shapes and position them on screen without having to calculate the individual position of every coordinate for every shape. These functions are used to create local drawing states, for now you can thing of them as local coordinate systems that apply only to the shapes in between these two functions. This notion is quite important and it will come back later when we move into 3D graphics and animation.
When you see a push()
and pop()
operation you can normally read it as "ok, this person is changing the frame of reference so that the (0,0) positions is somewhere else in the screen now".
Let's look at some practical examples of how this looks like:
Observe how the instruction that draws the ellipse hasn't changed at all, same parameters, yet these two circles are drawn at different positions. The trick is that the translate
statement changes the origin of the coordinate system, what we put in translate(x, y)
becomes our new (0,0)
.
The rotate()
statement when given only one parameter will rotate our coordinate system by whatever angle we give it.
Let's draw the clock again using push()
, pop()
, translate()
, and rotate()
.
At first it might seem boringly simple but push
and pop
have a hidden superpower in that they can be compounded, to create recursive visuals that can be visually quite complex.
If you are confident with Javascript the command line and working with your own editor you can try Pantera. For this course we have created a stand-alone p5js application bootstrap called Pantera, you can find it here.
push()/pop()
example was adapted from code in OpenProcessing by Jose Concha. Thanks!
Cheatsheets by Ryo Sakai @ryodejaneiro and Ben Moren. Thank you very much!
The dynamic poster compositions are largely inspired by published work from Studio Naam
What you see in this experiment is a dynamic version of the Josef Albers illustration, where we simply adjust the saturation of the colours in the background without the smaller squares. Observe how your eye interprets the relationship between the colors as the colors fade.
Here you can find a couple of poster designs for a digital release, you can think of these as quick loops running in public screens. This design has quite a graphic look, with a grid layout composed of 6 rows and 3 columns. The middle row with the dynamic graphic elements is the only thing that changes, both designs are made using p5js primitives, namely arc()
in the case of th first design and and rect()
in the case of the second. What appears to the eye as a fancy animation is a sin()
wave that modulates the sizes of our drawn elements.