00:00/00:00TIME LEFT
Choosing the a bcd

Shortcuts ⌨️

  • SPACE to play / pause
  • ARROW RIGHT or L to go forward
  • ARROW LEFT or J to go backward
  • ARROW UP to increase volume
  • ARROW DOWN to decrease volume
  • F to toggle fullscreen
  • M to toggle mute
  • 0 to 9 to go to the corresponding part of the video
  • SHIFT + , to decrease playback speed
  • SHIFT + . or ; to increase playback speed

Want to learn more? 👋


That's the end of the free part 😔

To get access to 71 hours of video, a members-only Discord server and future updates, join us for only $95!

Next lesson

Scroll based animation

Difficulty Medium

Starter packFinal project

Introduction 00:00

Having an experience composed of only WebGL is great, but sometimes, you'll want the experience to be part of a classic website.

The experience can be in the background to add some beauty to the page, but then, you'll want that experience to integrate properly with the HTML content.

In this lesson, we will learn how to use Three.js as a background of a classic HTML page. We will make the camera translate to follow the scroll. We will discover some tricks to make this scroll more immersive. We will add a cool parallax animation based on the cursor position. And finally, we will trigger some animations when arriving at the corresponding sections.

Setup 01:11

This lesson is a good opportunity to practice many of the techniques we've already learned. Instead of having the environment already set up, we will do most of the work ourselves.

The OrbitControls has been removed because we want the camera to move according to the scroll and to not let the user rotate about as in prior lessons.

Dat.GUI is already available and one color as been created to be used later:

 * Debug
const gui = new dat.GUI()

const parameters = {
    materialColor: '#ffeded'

gui.addColor(parameters, 'materialColor')

Some very simple HTML content has been setup already. Currently, you can only see one title but there are two sections right below. We can't see them because the scroll is blocked.

HTML Scroll 02:05

Activate the scroll

Earlier in the course, we deactivated the scroll using this CSS:

    overflow: hidden;

To reactivate it, remove the overflow line in /src/style.css.

You should be able to scroll and see the two other sections below.

Fix the elastic scroll

In some environments, you might notice that, if you scroll too far, you get a kind of elastic animation when the page goes beyond the limit.

While this is a cool feature, by default, the back of the page is white and doesn't match our experience.

To fix that, we could have set the background-color of the page to the same color as the clearColor of the renderer . Instead, we are going to make the clearColor transparent and only set the background-color on the page.

To do that, in /src/script.js, you need to set the alpha property to true on the WebGLRenderer:

const renderer = new THREE.WebGLRenderer({
    canvas: canvas,
    alpha: true

By default, the clear alpha value is 0 which is why we didn't have to set it ourselves. Telling the renderer to handle alpha is enough. But if you want to change that value, you can do it with setClearAlpha:


We can now see the back of the page which is white.

In /src/style.css, add a background-color to the html in CSS:

    background: #1e1a20;

We get a nice uniform background color and the elastic scroll isn't an issue anymore.

Objects 06:51

We are going to create an object for each section to illustrate each of them.

To keep things simple, we will use Three.js primitives, but you can create whatever you want. And later in the course, you'll learn how to import custom models into the scene.

In /src/script.js, remove the code for the cube. In its place, create three Meshes using a TorusGeometry, a ConeGeometry and a TorusKnotGeometry:

 * Objects
// Meshes
const mesh1 = new THREE.Mesh(
    new THREE.TorusGeometry(1, 0.4, 16, 60),
    new THREE.MeshBasicMaterial({ color: '#ff0000' })
const mesh2 = new THREE.Mesh(
    new THREE.ConeGeometry(1, 2, 32),
    new THREE.MeshBasicMaterial({ color: '#ff0000' })
const mesh3 = new THREE.Mesh(
    new THREE.TorusKnotGeometry(0.8, 0.35, 100, 16),
    new THREE.MeshBasicMaterial({ color: '#ff0000' })

scene.add(mesh1, mesh2, mesh3)

All the objects are, of course, on top of each other. We will fix that later.

Again, in order to keep things simple, our code will be a bit redundant. But don't hesitate to use arrays or other code structuring solutions if you have more sections.


Base material

We are going to use the MeshToonMaterial on all three Meshes.

As in the Materials lesson, we are going to create one instance of the material and use it for all three Meshes.

When creating the MeshToonMaterial, use the parameters.materialColor for the color property and apply it to all 3 Meshes:

// Material
const material = new THREE.MeshToonMaterial({ color: parameters.materialColor })

// Meshes
const mesh1 = new THREE.Mesh(
    new THREE.TorusGeometry(1, 0.4, 16, 60),
const mesh2 = new THREE.Mesh(
    new THREE.ConeGeometry(1, 2, 32),
const mesh3 = new THREE.Mesh(
    new THREE.TorusKnotGeometry(0.8, 0.35, 100, 16),

scene.add(mesh1, mesh2, mesh3)

Unfortunately, it seems that the objects are now black.

The reason is that the MeshToonMaterial is one of the Three.js materials that appears only when there is light.


Add one DirectionalLight to the scene:

 * Lights
const directionalLight = new THREE.DirectionalLight('#ffffff', 1)
directionalLight.position.set(1, 1, 0)

You should now see your objects.

We are using the color stored in the parameters object, but changing this value with the Tweaker doesn't change the material itself.

To fix that, we can listen to the change event on the already existing tweak and update the material accordingly:

    .addColor(parameters, 'materialColor')
    .onChange(() =>

Gradient texture

As we saw in the Materials lesson, by default, the MeshToonMaterial will have one color for the part in the light and one darker color for the part in the shade.

We can improve that by providing a gradient texture.

Two gradient images are provided in the /static/textures/gradients/ folder.

Instantiate the TextureLoader before instantiating the material. Then load the textures/gradients/3.jpg texture:

// Texture
const textureLoader = new THREE.TextureLoader()
const gradientTexture = textureLoader.load('textures/gradients/3.jpg')

Use it in the gradientMap property of the material:

// Material
const material = new THREE.MeshToonMaterial({
    color: parameters.materialColor,
    gradientMap: gradientTexture

Not the toon effect we were expecting.

The reason is that the texture is a very small image composed of 3 pixels going from dark to bright. By default, instead of picking the nearest pixel on the texture, WebGL will try to interpolate the pixels. That's usually a good idea for the look of our experiences, but in this case, it creates a gradient instead of a toon effect.

To fix that, we need to set the magFilter of the texture to THREE.NearestFilter so that the closest pixel is used without interpolating it with neighbor pixels:

const gradientTexture = textureLoader.load('textures/gradients/3.jpg')
gradientTexture.magFilter = THREE.NearestFilter

Much better, but we still need to position the meshes properly.

Want to learn more?

That's the end of the free part 😔

To get access to 71 hours of video, a members-only Discord server and future updates, join us for only $95!

How to use it 🤔

  • Download the Starter pack or Final project
    (if your browser detects a menace, do not worry, it is not)
  • Unzip it
  • Open your terminal and go to the unzip folder
  • Run npm install to install dependencies
    (if your terminal warn you about vulnerabilities, ignore it)
  • Run npm run dev to launch the local server
    (your browser should start automatically)
  • Start coding
  • The JS is located in src/script.js
  • The HTML is located in src/index.html
  • The CSS is located in src/style.css

If you get stuck and need help, join the members-only Discord server: