As we were exploring options on how to best illustrate a brand story recently, we came up with a pretty interesting prototype: a multi-step animation that starts with a rotating globe full of particles.

From a technical perspective, that first step was definitely the most interesting one. Because as all the following animation steps were plain 2D, I couldn’t use a 3D renderer such as Three.js. And so I had to figure out how to render a 3D shape using only the Canvas 2D API.

In this article, I’ll show you how I finally did it. I’ll first explain how to render a basic shape from a 3D scene using the JavaScript Canvas 2D API. Then in the second part of this post, I’ll show you how to make everything a bit fancier with some textures and 3D volumes.

1. Setup the canvas scene To get started, we need to add a canvas element in our HTML. We also need to add an id so it will be easier to select it from JavaScript. That’s all we need in the HTML! For the CSS, we need to remove the default margins on the body and prevent scrollbars from appearing by using ‘overflow: hidden;’. Because we want our canvas to be full screen, we define its width and height at 100% of the viewport. To setup a full screen Canvas demo in JavaScript, we need to select the canvas from the DOM and get its dimensions. The rest of the setup is mostly about handling the user resizing their screen.

2. Create particles To easily manage a high number of particles in JavaScript, the easiest way is by using a class (introduced with ECMAScript 2015). This will allow us to define random properties for every particle while still sharing common methods between all of them. The first part of a class is the constructor method and we use it to store the custom properties of each particle. We also create two methods for our dots: project() and draw(). The project method is were the magic happens: we convert the 3D coordinates of our particle to a 2D world. Finally, the draw method is where we draw the particle on our canvas after we calculate the projected values. You may have noticed we are using a PERSPECTIVE constant. This value will define the behavior of the ‘camera’ we are simulating in the project method. If you increase its value, you will notice that the perspective is getting less and less visible, everything will look flat. If you do the opposite and lower the value, the perspective will be much more intense. Click here to see the video. (mp4, 382.11 kB)

3. Render the scene Now that we have all our particles ready to be rendered on the screen, we need to create a small function that loops through all the dots and renders them on the canvas. If you try the code as is, you will get something very static because we are not moving the dots in our 3D scene. Current result of the code To make everything move there are many options and there is no perfect solution. In my case I’m going to use the TweenMax plugin made by GreenSock because it doesn’t require a lot of setup and is pretty straightforward. See the Pen 3D World in 2D (Pure canvas) by Base Design (@basedesign) on CodePen. You may think it is not that interesting to write so much for so little. We could get away with simulating the complex math. But, now that the hard part is done, we can go crazy!

Making a globe To create a globe out of particles, we need to calculate their coordinates on its surface. The coordinates of a point along a sphere don’t use the classic Cartesian Coordinates (x, y, z) but three different values from the Polar Coordinate System: Radius : The radius of the sphere

: The radius of the sphere Theta : The polar angle (between 0 and 360°)

: The polar angle (between 0 and 360°) Phi: The azimuth angle (between -90° and 90°) © Source: zemax.com In our case we want random values for every dot, so we will define the Theta and Phi values randomly when we create a new one. The radius being the same for every dot we can store in into a global variable. You may be wondering why we are not using a more basic way to generate a random value for Phi. If we were to do so, the distribution along the sphere wouldn’t be uniform and would display more particles around the poles. This is why we are using using an Arc Cosine (acos) function to fix this issue. Comparison of a basic random function and a one using Arc Cosinus Finally we need to convert the coordinates from Polar to Cartesian to match our current 3D world every time we want to project the dot on the 2D canvas. We are also drawing it using a circle this time, instead of a rectangle.

Depth sorting So far we have only used a basic monochrome shape from the Canvas API. If we want to use a custom texture for our particles, we face a problem.

In the render function we are drawing the particles based on their order in the dots array. When we use textures it will become noticeable that we are drawing particles on top of others, without taking their depth value into account. This will create a weird result with further particles being displayed over closer ones. Click here to see the video. (mp4, 2.79 MB) To fix this issue, we need to sort the array of dots based on their Z position right before we draw them so the furthest dots will be drawn first and the closest ones last. We call this method Depth Sorting! See the Pen 3D Globe in 2D (Depth sorting) by Base Design (@basedesign) on CodePen.