Quick guide into creative coding with p5.js

A beginner friendly introduction to generative art

Diego Corona
13 min readJul 23, 2020

In this tutorial I want to introduce you to the concept of creative coding by creating a ‘fluid/natural’- like animation using some code and some simple dots. The idea is to end-up creating something like the image below in the span of two quick tutorials.

P.S. You don’t need to have any coding experience to create this

Dots in space moving in a fluid manner
Our end goal !!!

I’ve always been attracted by the mesmerising digital art created by brilliant modern artists, and thus, I could not help to wonder how do they do it? And with this question I went into a rabbit hole of graphic design software, 3D rendering and more.

This lead me to the question of to which of these solutions should I dedicate my attention and time?

While part of the answer depends on what you are trying to achieve, I decided to go with what I was more familiar with… programming.

In my quest to become a digital artist, or at least paint some lines on a computer screen, I found processing. This fantastic programming framework and language that is heavily focused on making visual arts processes simple. Arguably, I found it a bit difficult to find processing related stuff just by googling (I think it might be due to its name), but if you go to their main website processing.org you can find plenty of information on how to get started.

To keep things simple these set of tutorials use the JavaScript version of processing, p5.js. I’m choosing this particular version, so that this tutorial does not requires you to lose time downloading and settting up stuff.

The only thing that you will need is a browser.

00 — The main blocks of p5.js

Let’s get started using the online editor at: https://editor.p5js.org/

When opening for the first time you will see two blocks of code, this is the ‘core’ of the processing framework. The scope of these blocks is defined by the brackets { }. Press the play button at the top and you should see what this initial piece of code produces.

p5.js web editor, starting screen
Screen that you will see the first time you open the p5.js editor

setup( ) is a block that runs only one time, while draw( ) encloses the code that will be running in an infinite loop until you close your browser tab or the browser entirely.

This way we can think of setup as a block where we can put any initial conditions that we might think of. In this case we already have the function createCanvas( ). As its intuitive name implies, this function defines the size of our drawing space.

createCanvas( ) takes the arguments width and height, thus, at the beginning we have a drawing canvas of 400 x 400 (width x height).

One of the cool things about p5.js is not only that is quite intuitive, but also, it has special variables that gives us access to the information in the browser. To show you this, let us make a more interactive canvas, one that is defined by the current aspect ratio of your browser. Lets change the createCanvas( ) function to:

createCanvas(windowWidth, windowHeight);

IMPORTANT! Be sure to write everything correctly. The web browser is still not intelligent enough to understand what you might mean when passing a line of code with a typo, so we have to be extra careful with our syntax.

The above code creates a canvas that fills the space in the preview section according to the size of your browser, thus is you change the size of your browser and press play you see how it changes according to this.

Next, in the draw( ) section we have the function background( ). As you can imagine by its name, this function will define the colour of background, and the sky is the limit!!

You can put any colour that you want as long as you know its RGB value. This means that this function can take multiple arguments, but inline with keeping things simple, let us use a nice greyish colour for our first animation.

Let’s use:

background(50);

Now let’s spice up our canvas. We are going to draw a circle, by using….. you guessed it! The circle( ) function.

circle(x,y,d), takes two positional arguments and one for its size. So let us add a circle in the middle of the screen by using our special variables ‘width’ and ‘height’ that correspond to the size of the canvas. To do this we need to add the following line right after background( ):

circle(width/2, height/2, 50);

Dividing our special variables by two, we position the circle in the middle of the canvas. The 50 is just a random size for the circle that I chose. You can try to change the size by changing this value.

circle in the middle of screen
Congrats!!! we have drawn our first circle!

With this there are two important things to notice:

First, the coordinate system in the processing framework is different from our conventional system.

In the traditional cartesian system we are going to find the coordinate x = 0 & y =0 right at the centre of our cartesian grid.

This is because we can have both negative and positive values.

Sometimes this way of thinking does not make sense from the perspective of a screen, thus, the coordinate system in the processing framework is reversed; meaning that starts from the top left corner of your canvas. So as you start going down the canvas the y-axis value increases and if you start moving to the right the x-axis value will increase. A visual representation of this might help understand things better.

Differences between a traditional cartesian plane and the canvas plane

Secondly, you might be thinking, why not use windowWidth and windowHeight instead of width and height?

And I would suggest you to try it by yourself. One set of variables is related to the canvas and the other to the window size of the browser. Since we only created our canvas once, this means that using width and height, we’ll keep our circle centred even if we change the browser size. But if we use the window related variables, the circle will change its position according to the browser size and not the canvas, thus, it will not be cantered in a logical manner.

Remember, draw( ) is running forever. This means that our circle its getting drawn over and over again along with the background, meaning that our browser is doing this work over and over again.

So for instance if we move the above code to the setup( ) section we would obtain the same result, but the browser would draw our circle once.

Hopefully, you can see why we might want to do this. When creating animations, we essentially need to draw over our canvas over and over again, if we draw the background time and time again you can imagine it as creating a new canvas that is going to represent a new frame for our animation.

01 — Making our circle go around in circles

A circle going around in a circular motion

Ok, so now let’s get started with creating our first animation. We are going to make this circle move with a circular motion. To make things easy we are going to use some basic trigonometry functions that are quite popular when drawing circles… sine and cosine.

To create a unitary circular path we have to travel from 0 to TWO_PI, and I’m writing 2π like that, because as you can imagine is also a special variable in p5.js.

Now, let’s say our trajectory has a radius of 50, thus we can write:

circle(width/2 + 50*cos(TWO_PI), 50*sin( height/2 + TWO_PI) , 50);

This has one big problem…. it does not move. For us to create an animation, we need to create frames and they need some sort of timing.

Luckily, p5.js has this special variable called frameCount, that allows us to track the frames that have been created since we started the program. This way we can create a timing for our frames so that our animation looks smooth, so we are going to calculate our timing with a simple division

frameCount / (number of frames)

number of frames represent how many frames we want to achieve. So, to do this let me introduce you to the concept of variables.

A variable is just a place for us to store information like numbers, characters, bits… well you get the idea. In JavaScript the best way to do to define new variables is by using the directive ‘let’ followed by the name of the variable, there are other ways, but don’t worry about that at this point, let us use ‘let’. So we are going to define our desired number of frames like:

let nFrames = 200;function setup(){
...
}
function draw(){
...
}

The important thing here is the scope, nFrames is outside setup() and draw(), this means that both have access to this variable, while, timing can only be used inside draw. This way we can create a variable that is specific to the timing of our animation (i.e. a variable that is going to store the result of the division that we mentioned earlier), let’s call it timing. We will use this variable to move our circle around, so we should add the following in the draw() block:

let timing = frameCount / nFrames;circle(width/2 + 50*cos(TWO_PI*timing), 50*sin( height/2 + TWO_PI*timing) , 50);

Next, lets keep things clean, after all one of the issues with programming is that it can get hard to read quite easily. So using the new found power of creating variables, we can rewrite our code specifing a variables for our x and y coordinates as well as one variable to specify the diameter of our circle. We will have something like this:

let nFrames = 200;function setup(){
createCanvas(windowWidth, windowHeight);
}
function draw() {
let timing = (frameCount) / nFrames;
background(50);

let x = width/2 + 50*cos(TWO_PI*timing);
let y = height/2 + 50*sin(TWO_PI*timing);
let diameter = 50;

circle(x, y, diameter);
}

A bit more of code for sure, but easier to read. Next, let’s next add another circle, so I can introduce you to the concept that is going to make possible the creation of the animation at the beginning of this post. Try it for yourself! but in case you’re in a rush, here’s the code to do add another circle:

let nFrames = 200;function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
let timing = (frameCount) / nFrames;
let diameter = 10;
background(50);

let xTwo = 3*width/4 + 50*cos(TWO_PI*timing);
let yTwo = height/2 + 100*sin(TWO_PI*timing);
let xOne = width/4 + 50*cos(TWO_PI*timing);
let yOne = height/2 + 20*sin(TWO_PI*timing);
circle(xTwo,yTwo, diameter);
circle(xOne,yOne, diameter);
}
two circles travelling with different circular motions
The result!!!

Don’t worry too much about the values. I’m just changing how our circles move, so that we have two circles that move in different trajectories. You can play around with these values, so that you create some interesting patterns.

02 — Making our circle go around in circles

We can use the movement of our circle to create a line that moves with this motion. For this purpose we are going to use dots to give this sort of a gradient feel, like shadows of white. Because at the end of the day we can think of a line as a continuous succession of points.

So we will be using the function point(x,y) to create our dots, but to draw a line that goes from circle to circle we can use a trick called ‘linear interpolation’.

We won’t go into the maths behind this operation, but essentially this will help us draw our line. We are going to use the lerp(val1, val2, percentage) function. This function will help us go from val1 to val2 by a percentage of that distance.

This can be difficult to understand so let us write a quick example. Let’s get rid of the timing, so that we have only two stationary circles, and then make circle one go to where circle two is by using small steps with the lerp function.

Circle one approaching circle two using small steps

Here I moved things a little, we make xOne start at the edge of the screen and then approach xTwo using the lerp function approaching at 5% steps, using:

xOne = lerp(xOne, xTwo, 0.05);

If you make this percentage smaller, it will take longer for circle one to get to circle two, and viceversa. If you want to try to get the above result, you can use the following code:

let nFrames = 200;
let xOne = 0;
function setup() {
...
}
function draw() {
let timing = (frameCount) / nFrames;
background(50);
let xTwo = 3*width/4 + 50*cos(TWO_PI);
let yTwo = height/2 + 100*sin(TWO_PI);

xOne = lerp(xOne, xTwo, 0.05);
let yOne = height/2 + 20*sin(TWO_PI);
circle(xTwo,yTwo, 10);
circle(xOne,yOne, 10);
}

Now back to our original code, we are going to use the lerp() function to position dots between our circles so that we create a line. So we are going to create several dots each position at an specific percentage of the distance between our two points.

First things first, this means calculating the x-axis position like we did previously with lerp, and now in a similar way we are going to calculate the y-axis position by using:

lerp(yOne, yTwo, percentage);

Since a line is a combination of multiple dots, we need several of them, thus, the best way of creating them is through a ‘for-loop’. If you’re new to this concept, this means that we will be repeating some lines of code ‘for’ an specified number of times. Essentially the code looks like:

let nFrames = 200;
let nPoints = 500;
function setup() {

}
function draw() {
circle(xTwo,yTwo, diameter);
circle(xOne,yOne, diameter);
stroke(255);
for(let counter = 0; counter < nPoints; counter += 1){ let percentage = counter/nPoints;
let xLerped = lerp(xOne, xTwo, percentage);
let yLerped = lerp(yOne, yTwo, percentage);

point(xLerped, yLerped);
}

First, we define at the beginning how many dots we want to draw, in this case 500 points (as seen in the new variable nPoints). As any block of code, the code inside the for-loop is enclosed in brackets.

Let’s first analyse the code inside of it. It similar to what we have previously done, the only difference is that we are calculating the y-axis position and using the point() function to create a dot inside the canvas.

Now, if you are new to for-loops, you shouldn’t be scared by them as they are quite simple in nature.

The for-loop has three main elements, the first one is a piece of code that is executed one time, thus, it’s the perfect place to create a variable that will keep track of how many time the for loop has run.

The second part is the loop condition, if the condition is met the loop will continue running; in our case since we want to create 500 points our condition is that if the counter has not reached 500 then we should continue to run the loop.

Finally, the third part is a piece of code that gets executed every time that we reach the end of the loop. Thus, it is a good place to increase our counter.

With this in mind we can make more sense of the code presented. We start at point 0 and we calculate the percentage as ratio of our number of points. This means that for instance if we have 500 points the 50th point should be at 10% of the total distance between our circles. So we get the following result:

Two circles moving with a circular motion and a line connecting them
Our line following the motion of our circles

You migh t have noticed that I used a special function called stroke(). This function defines the colour of the elements to be drawn. It’s similar to background in the sense that it can take an RGB value, in our case we are using 255 to indicate that we want our dots to be white.

03 — Spicing things up

Let’s make things more interesting, at the moment our line follows the trajectory of our circles directly. But lets imagine that our dots are slow to keep up with this trajectory. To do that, we are going to make our dots follow slower circles (we are not going to draw them, but this the concept in essence). We are going to create a delay variable so that we can play around with it and find a particular delay that captivate us. Each point has to be delayed accordingly, so we can say that our delayed times is going to be:

let delayedTime = (timing - delay*percentage);

Therefore, lets say we want to delay circle two trajectory, we can write then:

let xTwoDelayed = 3*width/4 + 200*cos(TWO_PI*delayedTiming);
let yTwoDelayed = height/2 + 100*sin(TWO_PI*delayedTiming);

And we can used this slower circular trajectory to calculated our path using:

let xLerped = lerp(xOne, xTwoDelayed, percentage);
let yLerped = lerp(yOne, yTwoDelayed, percentage);

This will get us the following result:

Two circles moving in a circular motion with a spiral-like line connecting them
Our line of dots with a delay

Now it’s your time to try adding a delay on both circles and see what result you get.

You can play with all the variables created to get some nice results.

As a sidenote, I always like to add a bit of transparency to the dots so that the animation looks smoother, you can do so by adding the transparency value to our stroke, using stroke(255, 50).

This code is by no means written to be effective or clean, but in the next tutorial we will deal with cleaning up our mess and making the animation look more ‘natural’, like the image at the beginning of this tutorial.

Stay tuned and creative!!

(The complete code should look like:)

let nFrames = 200;
let nPoints = 500;
let delay = 3.0;
function setup() {
createCanvas(windowWidth, windowHeight);
stroke(255, 50);
}
function draw() {
let timing = (frameCount) / nFrames;
let diameter = 10;
background(50);
let xTwo = 3*width/4 + 200*cos(TWO_PI*timing);
let yTwo = height/2 + 100*sin(TWO_PI*timing);
let xOne = width/4 + 50*cos(TWO_PI*timing);
let yOne = height/2 + 20*sin(TWO_PI*timing);
circle(xTwo,yTwo, diameter);
circle(xOne,yOne, diameter);
for(let counter = 0; counter < nPoints; counter += 1){

let percentage = counter/nPoints;
let delayedTiming = (timing-delay*percentage);

let xTwoDelayed = 3*width/4 + 200*cos(TWO_PI*delayedTiming);
let yTwoDelayed = height/2 + 100*sin(TWO_PI*delayedTiming);

let xLerped = lerp(xOne, xTwoDelayed, percentage);
let yLerped = lerp(yOne, yTwoDelayed, percentage);
point(xLerped, yLerped);
}
}

--

--

Diego Corona

Electronics and Data Engineer interested in all things plant related 🌱, with a soft spot for generative art.