Understanding and Implementing Three.js with Angular and creating a 3D Animation
Tags: angular, webdev, tutorial, threejs
Published: 2022-11-03 21:01:39

Hey guys, how are you!?

Today I'll talk about Three.js and how we can implement it with Angular! And after that, we'll create a simple 3D animation from Three.js! 

So, let's start! First, let's see what Three.js is!

## 🎯 Three.js in a nutshell:
Three.js is an amazing javascript library, you can do amazing things on your website and make it powerful, beautiful! Three.js lets you work with 3D geometry, objects, lights, textures and some other cool stuff! And Three.js has three important things: scene, camera and renderer!

Take a look at [these examples](https://threejs.org/examples/#webgl_animation_keyframes) and let your mind wander through so many possibilities you can create!

Now, let's work 👩🏽‍🎨 🧑🏽‍🎨!
___
## Installing dependencies:
```
👇🏻 Install on the project
npm install three
``` 
```
👇🏻 Install on the project
npm i @types/three
```  
___
## Generating and importing the Canvas Box Component:
Let's create our canvas component, so with `Angular CLI` we'll  create our component:
 ```
👇🏻 Create the canvas component
ng g c components/canvas-box
```  
And importing it in our `app.module.ts`:

```typescript
...
import { CanvasBoxComponent } from './components/canvas-box/canvas-box.component';

@NgModule({
  imports: [
    BrowserModule
  ],
  declarations: [
    AppComponent, 
    CanvasBoxComponent
  ]
})
...
``` 
___
## Implementing and Understanding the Canvas Box structure:
First, in our `canvas-box.component.html` we need to create the canvas html structure and add an id to our canvas, so the structure is like below: 
```html
<canvas id="canvas-box"></canvas>
```
Now, let's create the magic with three.js, in our `canvas-box.component.ts` let's create a `createThreeJsBox()` method, add in our `ngOnInit()` Lifecycle hooks and import the Three.js :
```typescript
...
import * as THREE from 'three';

export class CanvasBoxComponent implements OnInit {
 ngOnInit(): void {
  this.createThreeJsBox();
 }

 createThreeJsBox(): void {
 }
}
...
```
We need to get our canvas element html, so let's create a const and get the element: 
```typescript
...
export class CanvasBoxComponent implements OnInit {
 ...
 createThreeJsBox(): void {
    const canvas = document.getElementById('canvas-box');
 }
}
...
```
### ⚠️ Important
> `document.getElementById` is not good to use with Angular because we have SSR! I only used it because in my opinion it is the simplest and most used way! If you want to use it, I recommend to make a validation of if it's SSR side and if it is, exit the function! Here I have an installable package that you can do this validation: [ngx-verify-is-server-side](https://www.npmjs.com/package/ngx-verify-is-server-side).

___
In the next steps we are going to create our scene, material and two types of lights to place in our scene! So, let's create some const's and the code looks like below:

```typescript
...
export class CanvasBoxComponent implements OnInit {
 ...
 createThreeJsBox(): void {
    const canvas = document.getElementById('canvas-box');

    const scene = new THREE.Scene();

    const material = new THREE.MeshToonMaterial();

    const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
    scene.add(ambientLight);

    const pointLight = new THREE.PointLight(0xffffff, 0.5);
    pointLight.position.x = 2;
    pointLight.position.y = 2;
    pointLight.position.z = 2;
    scene.add(pointLight);
 }
}
```
### 👨🏽‍🏫👩🏽‍🏫 Understanding the code:
```javascript
const scene = new THREE.Scene()
```
Here we declare a `THREE.Scene()`, the scene is necessary and important for us, because with it we can show our animation! The scene is like our "animation scene" and everything we create we need to add to our scene!

```javascript
const material = new THREE.MeshToonMaterial()
```
We declare our material canvas, the `material` "is like" the css of our 3D element! Three.js has a few types of stuff, [see here](https://threejs.org/docs/index.html#api/en/materials/LineBasicMaterial)

```javascript
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
```
We declare a `THREE.AmbientLight` in our scene, set the color and intensity of our light and add the ambientLight to our scene.

```javascript
const pointLight = new THREE.PointLight(0xffffff, 0.5);
pointLight.position.x = 2;
pointLight.position.y = 2;
pointLight.position.z = 2;
scene.add(pointLight);
```
We create a new `THREE.PointLight` declaring the color and intensity, change the position of the `pointLight` and add it in to our `scene`.
___
Now let's create our 3D geometries and add it to our scene, let's create a Box and a Torus Geometry and the code is like below:

```typescript
...
export class CanvasBoxComponent implements OnInit {
 ...
 createThreeJsBox(): void {
    
   /*The our code is here, I removed it because of the total lines*/
   ...

   const box = new THREE.Mesh(
      new THREE.BoxGeometry(1.5, 1.5, 1.5), 
      material
   );

   const torus = new THREE.Mesh(
      new THREE.TorusGeometry(5, 1.5, 16, 100),
      material
   );

   scene.add(torus, box);
 }
}
```
### 👨🏽‍🏫👩🏽‍🏫 Understanding the code:
We create two 3D geometries by declaring the properties of each geometry, add material("css") to our geometry and then add the geometry to our scene.
___ 
In the next steps we will declare the screen sizes, create the camera and renderer! The code is as below:
```typescript
...
export class CanvasBoxComponent implements OnInit {
 ...
 createThreeJsBox(): void {
       
   /*The our code is here, I removed it because of the total lines*/
   ...

   const canvasSizes = {
    width: window.innerWidth,
    height: window.innerHeight,
   };

   const camera = new THREE.PerspectiveCamera(
    75,
    canvasSizes.width / canvasSizes.height,
    0.001,
    1000
   );
   camera.position.z = 30;
   scene.add(camera);

   if (!canvas) {
    return;
   }

   const renderer = new THREE.WebGLRenderer({
    canvas: canvas,
   });
   renderer.setClearColor(0xe232222, 1);
   renderer.setSize(canvasSizes.width, canvasSizes.height);
 }
}
```
### 👨🏽‍🏫👩🏽‍🏫 Understanding the code:
```javascript
const canvasSizes = {
  width: window.innerWidth,
  height: window.innerHeight,
};
```
Create an object with `width` and `height` properties based in the window.

```javascript
const camera = new THREE.PerspectiveCamera(
  75,
  canvasSizes.width / canvasSizes.height,
  0.001,
  1000
);
camera.position.z = 30;
scene.add(camera);
```
We create our camera, the camera is like what the human eye sees. It is the most common projection mode used for rendering a 3D scene and declare the `THREE.PerspectiveCamera` properties, de `fov: 75`, `aspect: canvasSizes.width / canvasSizes.height`, `near: 0.001` and `far: 1000`. And after that we change the `camera.position.z` and add the camera to our scene.
___
We need to make our scene responsive, so we need to update the `canvasSizes` object in the window resize event, so let's do that!
```typescript
...
export class CanvasBoxComponent implements OnInit {
 ...
 createThreeJsBox(): void {
     
   /*The our code is here, I removed it because of the total lines*/
   ...

  window.addEventListener('resize', () => {
    canvasSizes.width = window.innerWidth;
    canvasSizes.height = window.innerHeight;

    camera.aspect = canvasSizes.width / canvasSizes.height;
    camera.updateProjectionMatrix();

    renderer.setSize(canvasSizes.width, canvasSizes.height);
    renderer.render(scene, camera);
  });
 }
}
```
### 👨🏽‍🏫👩🏽‍🏫 Understanding the code:
We added the resize listener and we updated some important properties, the `canvasSizes` properties, the `camera.aspect`, the `camera.updateProjectionMatrix()` and the `renderer` properties. And after that we render the updated `scene` and `camera`.
___ 
Now let's animate our 3D geometries and make the scene cooler! We'll use the `THREE.Clock()`, `getElapsedTime()`, update de `X, Y, Z` properties of our geometries, update de `renderer` and call the animation again on the next frame with `requestAnimationFrame`, then the code is like below: 
```typescript
...
export class CanvasBoxComponent implements OnInit {
 ...
 createThreeJsBox(): void {
        
   /*The our code is here, I removed it because of the total lines*/
   ...

  const clock = new THREE.Clock();

  const animateGeometry = () => {
    const elapsedTime = clock.getElapsedTime();

    // Update animation objects
    box.rotation.x = elapsedTime;
    box.rotation.y = elapsedTime;
    box.rotation.z = elapsedTime;

    torus.rotation.x = -elapsedTime;
    torus.rotation.y = -elapsedTime;
    torus.rotation.z = -elapsedTime;

    // Render
    renderer.render(scene, camera);

    // Call animateGeometry again on the next frame
    window.requestAnimationFrame(animateGeometry);
  };

  animateGeometry();
 }
}
```
### 👨🏽‍🏫👩🏽‍🏫 Understanding the code:
```javascript
const clock = new THREE.Clock();
```
Crete a const with `THREE.Clock()`, it is for keeping track of time.
```javascript
const elapsedTime = clock.getElapsedTime();
```
We declare a new const to get the seconds passed since the clock started and sets and to animate the "velocity" of our animation.

```javascript
box.rotation.x = elapsedTime;
box.rotation.y = elapsedTime;
box.rotation.z = elapsedTime;

torus.rotation.x = -elapsedTime;
torus.rotation.y = -elapsedTime;
torus.rotation.z = -elapsedTime;
```
Here we animate our geometries changing the `X, Y, Z` positions. 

```javascript
renderer.render(scene, camera);
```

We render the scene, camera updated. 
```javascript
window.requestAnimationFrame(animateGeometry);
```

We declare our `animateGeometry` in `requestAnimationFrame` to do the our animation.
```javascript
animateGeometry();
```
Just calling our function :D
___

COOOLLL, we have a cooler Three.js implementation and after that we can make awesome geometries combinations! 😁😁🚀🚀💥 🔥

Now we have a basic understanding of Three.js and its structure!

Three.js is awesome and we have countless possibilities of things to do with it and make our sites powerful.

Here we can see our study case live example 🤘🏻:

{% embed https://stackblitz.com/edit/angular-ivy-cvxmyo?file=src%2Fapp%2Fcomponents%2Fcanvas-box%2Fcanvas-box.component.ts %}

That's all guys, I hope you like it and if any questions, suggestions or other topics, please comment!

See you later ✌🏼