skip to content
Back

3D Motion with p5js

05-2023

I love the simplicity of lines. In drawings, motions, creative development... So when the incredible motion designer in my team showed me this 👇 for the first time. I had no idea how to implement it, but loved it instantly.

I always look for the most native solution, meaning, if you can do it without any library or framework, in a performant way, go for it.

But I also knew that a cube shape meant 3D, and I am not a Webgl master (yet!).

This motion came with another one, a variant, which was simple lines interacting with the mouse. My first thought was using canvas API, and I did some proof of concept with it, but I wanted to simplify the code and make it readable and easy for anyone who might update it later. That's when p5js came into my mind. It handles animation very well, handle mouse interaction and it has Webgl support! It is also much more easier for anyone who is not yet comfortable with native Canvas API to play with it.

If you don't know yet about p5js, I recommand checking the learn page. You can also find many resources about it on the web.

p5js code structure

p5js has two main functions, the setup function and the draw function. The setup function is where you will declare your canvas and any needed variables or function to get you started. The draw function speak for itself, it will draw what you ask for, with default frame rate based on the frame rate of the display (mostly 60fps).

Note: During the project, I have used p5js object in ES6 class, but in the attempt of making the most understandable code I will share both p5js editor code with simple functions and an ES6 class version of each sketches.

Grid structure

Each time I start coding, I implement the bones, the structure of it. Here, we have a shape repeated in a row and column. So I start by implementing a grid structure, loops, that will repeat a given shape, here let's just make a rect() shape.

// we declare needed variables
const boxSize = 70;
let width, height;

function setup() {
  // create canvas with same size as our window
  width = window.innerWidth;
  height = window.innerHeight;
  createCanvas(width, height);
}

function draw() {
  // clear canvas for every draw
  clear();
  // add a white background
  background(255);

  // loop throught to create squares placed every 70px (boxSize)
  // on x & y axis
  for(let x = 0; x < width; x+=boxSize) {
    for(let y = 0; y < height; y+=boxSize) {
      rect(x, y, boxSize);
    }
  }
}
Codepen ES6 version

Simple shape to 3D shape

Now that we have our grid of squares, let's turn them into 3D shapes. You won't be able to see any difference before and after, just because the shape is actually just turn on front side so we don't see the depth of it.

Note: The position of the Canvas in WEBGL mode is centered. We will udpate it so it start on the top left corner as in 2D mode. There is other differences but this is the main one that you should know.

// inside the setup funcion we add WEBGL mode to the canvas
- createCanvas(width, height);
+ createCanvas(width, height, WEBGL);
// orthographic projection for the current camera
+ ortho();

// inside the draw funcion
  clear();
  background(255);
// top left corner canvas position
+ translate(-width / 2 + (boxSize/2), -height / 2 + (boxSize/2), 0);

  for(let x = 0; x < width; x+=boxSize) {
    for(let y = 0; y < height; y+=boxSize) {
-    rect(x, y, boxSize);
+    push();
+    translate(x, y);
+    box(boxSize);
+    pop();
    }
  }
Codepen ES6 version

Adding rotation

To be able to see any depth we will add an X rotation so the cube rotate on itself depending of the mouse. p5js enable a really nice and quick use of the mouse position with just mouseX and mouseY variables. (If you move your mouse over here 👇, you'll see the difference)

// inside for loop code
...
push();
+ rotateX(10);
+ rotateY(mouseX * 0.01);
translate(x, y);
...
Codepen ES6 version

Staggering the shapes

Now we can see that those are cube shapes by moving our mouse but they are all overlapping each other. We need to stagger them like a puzzle so when we move them it will perfectly fit with each other.

I have moved things a little bit. I have added different boxSize depending on the device width and encapsulated it all inside an updateGrid function. So now, I can call it whenever I want. The purpose of it is mainly to keep boxes placement always fitting each others.

Then, in the draw function I have declared an index variable and increment this index in my loop. This enable me to use a modulo operator to check wether a box should be stagger or not.

let width, height;
let isMobile = window.matchMedia('(max-width: 1024px)').matches;
let boxSize;
let rowStep;
let colStep;
let rows;
let cols;

function updateGrid() {
  boxSize = isMobile ? 40 : 70;
  rowStep = isMobile ? 57 : 100;
  colStep = isMobile ? 49 : 85;
  rows = (isMobile ? 26 : 37) * boxSize;
  cols = (isMobile ? 34 : 14) * boxSize;
}

function setup() {
  width = window.innerWidth;
  height = window.innerHeight;
  createCanvas(width, height, WEBGL);
  ortho();
  updateGrid();
}

function draw() {
  clear();
  translate(-width / 2 + (boxSize/2), -height / 2 + (boxSize/2), 0);
  background(255);
  
  let index = 0;
  
  for(let x = 0; x < rows; x+=rowStep) {
    for(let y = 0; y < cols; y+=colStep) {
      push();

      if (index % 2 == 0) {
        translate(x, y);
      } else if (isMobile) {
        translate(x + 29, y);
      } else {
        translate(x + 50, y);
      }

      rotateX(10);
      rotateY(mouseX * 0.01);
      box(boxSize);
      pop();

      index++;
    }
  }
}
Codepen ES6 version

Here we are! We have now this nice little cubes moving according to the mouse position, thanks to p5js!

Next step

As I mentioned earlier, in this project we had a few other things to play with. If you want to explore a little bit more with p5js, you can:

  • add a custom mouse pointer.
  • do a 2D variant with just small lines (in a grid) that rotates towards the mouse when the mouse move.

This are just ideas but don't limit yourself!

I hope you enjoyed this little step by step example of what is possible to do with p5js and that you will have some more fun with it!