Understanding and Implementing Three.js with Angular and creating a 3D Animation

Renan Ferro - Nov 3 '22 - - Dev Community

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 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
Enter fullscreen mode Exit fullscreen mode
๐Ÿ‘‡๐Ÿป Install on the project
npm i @types/three
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

And importing it in our app.module.ts:

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

@NgModule({
  imports: [
    BrowserModule
  ],
  declarations: [
    AppComponent, 
    CanvasBoxComponent
  ]
})
...
Enter fullscreen mode Exit fullscreen mode

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:

<canvas id="canvas-box"></canvas>
Enter fullscreen mode Exit fullscreen mode

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 :

...
import * as THREE from 'three';

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

 createThreeJsBox(): void {
 }
}
...
Enter fullscreen mode Exit fullscreen mode

We need to get our canvas element html, so let's create a const and get the element:

...
export class CanvasBoxComponent implements OnInit {
 ...
 createThreeJsBox(): void {
    const canvas = document.getElementById('canvas-box');
 }
}
...
Enter fullscreen mode Exit fullscreen mode

โš ๏ธ 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.


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:

...
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);
 }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘จ๐Ÿฝโ€๐Ÿซ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿซ Understanding the code:

const scene = new THREE.Scene()
Enter fullscreen mode Exit fullscreen mode

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!

const material = new THREE.MeshToonMaterial()
Enter fullscreen mode Exit fullscreen mode

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

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

We declare a THREE.AmbientLight in our scene, set the color and intensity of our light and add the ambientLight to our scene.

const pointLight = new THREE.PointLight(0xffffff, 0.5);
pointLight.position.x = 2;
pointLight.position.y = 2;
pointLight.position.z = 2;
scene.add(pointLight);
Enter fullscreen mode Exit fullscreen mode

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:

...
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);
 }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘จ๐Ÿฝโ€๐Ÿซ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿซ 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:

...
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);
 }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘จ๐Ÿฝโ€๐Ÿซ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿซ Understanding the code:

const canvasSizes = {
  width: window.innerWidth,
  height: window.innerHeight,
};
Enter fullscreen mode Exit fullscreen mode

Create an object with width and height properties based in the window.

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

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!

...
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);
  });
 }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘จ๐Ÿฝโ€๐Ÿซ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿซ 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:

...
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();
 }
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ‘จ๐Ÿฝโ€๐Ÿซ๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿซ Understanding the code:

const clock = new THREE.Clock();
Enter fullscreen mode Exit fullscreen mode

Crete a const with THREE.Clock(), it is for keeping track of time.

const elapsedTime = clock.getElapsedTime();
Enter fullscreen mode Exit fullscreen mode

We declare a new const to get the seconds passed since the clock started and sets and to animate the "velocity" of our animation.

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

torus.rotation.x = -elapsedTime;
torus.rotation.y = -elapsedTime;
torus.rotation.z = -elapsedTime;
Enter fullscreen mode Exit fullscreen mode

Here we animate our geometries changing the X, Y, Z positions.

renderer.render(scene, camera);
Enter fullscreen mode Exit fullscreen mode

We render the scene, camera updated.

window.requestAnimationFrame(animateGeometry);
Enter fullscreen mode Exit fullscreen mode

We declare our animateGeometry in requestAnimationFrame to do the our animation.

animateGeometry();
Enter fullscreen mode Exit fullscreen mode

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 ๐Ÿค˜๐Ÿป:

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

See you later โœŒ๐Ÿผ

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .