Three.JS Tutorial - Create 3D Environments with HTML and Javascript

three.js blog

Check out an example: Three.js Blog

See other awesome things you can do with three.js: Three.JS examples page

Table of Contents

Installations

Install Node.js

Node.JS is what makes the Javascript world go round.

"As an asynchronous event-driven JavaScript runtime, Node.js is designed to build scalable network applications." -Node.JS devs

Before downloading Node.JS, you should check to see if you already have it installed in your machine. To do this, we will open our Command Line.

VS code terminal

If you already have Node.JS installed, the terminal will print a version number that looks something like this: v0.10.35

If your terminal didn't print a version number, you need to go download Node.JS . (Don't worry, it's completely free - and if you really want to get into Javascript on any level, including professionally, Node.JS is a must-have.)

Install NPM

Now that you have Node.JS installed, allow me to introduce you to the wonderful world of the Node Package Manager (NPM) community. NPM allows brilliant developers to write awesome open-source Node.JS package modules that will allow you to do super cool things with Javascript, including make 3D websites. We need to install NPM at the command line in order to install the Three.JS package.

npm -v
        

Again, similar to when we checked for node.js, a version number will print if you already have npm installed.


            npm install -g npm

    

Now you're all set to create your new project repository!

Create your project folder and install VITE

This section revised: 2026-10. Unlike the original version of this tutorial, we are not going to use a generator that builds a project and sets up the required files for us. We will build the project by hand, so you can see every piece and know what it does.

First, choose a name for your project folder. Use lowercase letters and hyphens, with no spaces (for example, three-scene). Open the command line in the location where you want the project to live, and enter these commands separately:

mkdir three-scene
        
cd three-scene
        
npm init -y
        
npm install -D vite
        

What each command does:

You should now see package.json, package-lock.json, and a node_modules folder in your project.

Install Three.JS at command line

This section revised: 2026-10.

Make sure you are still at the root level of your project folder, then enter this command:

npm install three
        

This installs Three.JS as a dependency, meaning code your site actually uses. Open package.json and look at what changed: vite is listed under devDependencies, and three is listed under dependencies.

NPM generates and manages those for you. Humans should never edit them by hand, or you’ll destroy the planet. (Not really, but you will probably break your install. If that happens, delete node_modules and run npm install again.) package.json is different: we will edit it ourselves in the next step.

Important: When opening the project in VSCodium, just Open the folder. Do not create a new project, since that can overwrite the good work we did here in installation.

Edit package.json

Open package.json in your editor. Change "type": "commonjs" to "type": "module" (this lets our config file and our code use the same import syntax), and replace the "scripts" section so it looks like this:

{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  }
}
        

Make one more edit: Look further in the file for the "type" value (probably after "keywords", "author", and "license") and change it like so:

"type": "module", 

(At this point, Dr. B's package.json looks like this):

{
  "name": "three-js-2026",
  "version": "1.0.0",
  "description": "Sample three.js site for demonstration in DIGIT 400",
  "main": "index.js",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "keywords": [],
  "author": "Elisa Beshero-Bondar",
  "license": "ISC",
  "type": "module",
  "devDependencies": {
    "vite": "^8.3.4"
  },
  "dependencies": {
    "three": "^0.186.1"
  }
}

Leave the other entries (name, dependencies, and so on) as they are, but you can edit some of fields like "keywords" and "author" if you like. Get to know the three npm scripts you added! These are the commands you'll keep using for building and viewing your Node projects this semester. We typically add these script lines by hand to package.json in most Node projects.

Note: The npm commands only work if you're at the root of your project folder with package.json inside.

Create your project files

This section added in 2026-10.

Now open your project in the VSCodium editor. Just Open Folder and select your project directory.

Create these three files directly in your project folder, next to package.json:

three-scene/
├── index.html
├── main.js
├── style.css
├── package.json         (already there)
├── package-lock.json    (already there)
└── node_modules/        (already there)

Leave the three new files empty for now, and we will fill them in next. We will also add an images/ folder later, when we get to scene backgrounds.

Changes from the older version of this tutorial: If you followed the earlier version of this tutorial, you had some folders premade here, and those are gone in our new configuration. Notice what is not here: no src/ folder and no public/ folder. (In class, we moved your main.js and style.css out of the src/ directory.) Vite projects often use those directories, but we are keeping everything at the top (or "root") level so that the files you see in your project are the same files the browser asks for, whether you are working locally or looking at your published site.

Write your HTML

You'll need three basic elements in your HTML to render your 3D world:

Open the empty index.html file you just created and write this into it:

<!DOCTYPE html>
        <html>
        <head>
            <title>Three JS Demo</title>
            <meta charset="UTF-8" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
             <link rel="stylesheet" type="text/css" href="style.css" />
        </head>
        <body>
            <canvas id="bg"></canvas>
            <script type="module" src="main.js"></script>
        </body>
        </html>
      

The CSS is attached here with the <link> tag (in the usual way). (Other JavaScript framework projects might have you import it into the JavaScript that runs the show, but not this one.) Also note that the file names have no leading slash: style.css, not /style.css. Keeping paths relative like this is what lets the same project work locally and on your published site.

The canvas element has to remain empty! But HTML elements for headings and paragraphs can sit outside the canvas that hosts your built three.js scene. If you want to try making text float over your 3D scene, you can add a <main> element to hold it, after the canvas.

Create your background canvas by editing your CSS

In the same folder as your index.html file, open the empty style.css file you created. Write the following code into that CSS to make the <canvas> element in your HTML be the background for your site:

canvas{
          position:fixed;
          top: 0;
          left: 0;
        }
        

This is where your 3D objects will appear when we start creating them in your javascript.

HOW TO VIEW YOUR SITE LOCALLY

Since Three.JS requires dependencies in order to render, you need to tell your app to run in the command line in order to view it locally. You don't have any 3D objects yet, so for now you will just see a blank page, but this is the moment to confirm that your setup works.

In the command line terminal at your project folder, run this npm command:

           npm run dev
        
static dev server runnning

Click the link to your https://localhost:5173 dev server to preview your app, and voila! Your Vite project is set up and running locally. (The port number you are given might be different: mine is 5173.)

When you run the command npm run dev, whenever you make new edits to your code, your localhost browser will automatically refresh and reflect your changes. The dev server shows the files exactly as they sit in your project folder.

You will not be able to enter any new commands into your terminal until you kill the localhost serve process.

To “kill” or end your dev server process in your command line terminal, press CTRL + C in the terminal. (That's the Control key on Macs too, not Command.)

It will ask you if you want to "Terminate Batch Job". To confirm, type y and hit enter.

killing local server in command line

Javascript Time!

In your new main.js javascript file, you'll need to tell it to import the Three.JS node package module in order to reference the many pre-built 3D-building methods and objects.

To do this, simply paste these import instructions on the first line of your JS file:

import * as THREE from 'three';
        

Scene Setup

Helpful Three.JS documentation in case you need it

You need three main components to set up your Three.JS scene:

We create these objects by declaring a new const, a type of variable in Javascript, and defining it with new THREE methods.

// Setup

        const scene = new THREE.Scene();

        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);


        // create a new renderer by instating the canvas element in our HTML // file
        const renderer = new THREE.WebGLRenderer({
          canvas: document.querySelector('#bg'),
        });
      

You will not be able to view your new scene until you tell the renderer to render the scene and the camera with Three.js's .render method:

renderer.render(scene, camera);
        

Now that we have each of these objects set as constants, we can manipulate them with Three.JS's built-in methods.

Let's set the pixel ratio, the size, and the camera position:

renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(window.innerWidth, window.innerHeight);
        camera.position.setZ(50);
        camera.position.setX(-3);

       

Create your first object!

Now that our scene is set up, we can add 3D objects!

There are three basic components you need in order to create a 3D object in Three.JS:

We need to create new const variabels for each of these components so that we may manipulate them and add the final mesh to our scene.

Let's create a basic cube.

const geometry = new THREE.BoxGeometry(10, 10, 10);

        //set the color of the basic material in the object parameters `{}`

        const material = new THREE.MeshBasicMaterial( { color: 0xFF6347 } );

        const cube = new THREE.Mesh( geometry, material );
        

To add our cube into the scene, use the .add() method.

scene.add( cube );
        

Manipulating your object

You can change the position of the object by manipulating the cube's .position property and attaching the axis on which you want to move it:

cube.position.z = -15;
        cube.position.x = -15;
        

Change the rotation of your object by manipulating the .rotation property and attaching the axis on which you wish to rotate it:

cube.rotation.x = 2;
        cube.rotation.y = .5;
        

Lights and Material Types

Three.JS allows you to create objects with a wide variety of customizable material types and textures. Some material types require lights in the scene in order to be visible, including the Phong material. Your cube is visible because it is made of a Basic material, which is not affected by lights.

Let's create a new object to test out new materials:

const ico = new THREE.IcosahedronGeometry(10);
        const icoMaterial = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
        const icoMesh = new THREE.Mesh(ico, icoMaterial);

        scene.add(icoMesh);

        icoMesh.position.z= -15;
        icoMesh.position.x= 15;
        

If you try to view your new object in the browser, you will not be able to see it. Since Phong materials require light, you will need to at least one light object in your scene:

// Lights

        const pointLight = new THREE.PointLight(0xffffff);
        pointLight.position.set(0, -10, 10);

        const ambientLight = new THREE.AmbientLight(0xffffff);
        ambientLight.position.set(25, -15, -400);

        scene.add(pointLight);
        scene.add(ambientLight);
        

Watch what happens if you change your cube material to a StandardMaterial which accepts light, replacing it with BasicMaterial , which does not accept light:

const material = new THREE.MeshStandardMaterial( { color: 0xFF6347 } );
        
standard material cube

Animate your scene

To make your objects move through time, we need to create a new animate function and set our animation properties within it.

You can animate just about any property of an object you want.

function animate() {
            requestAnimationFrame( animate );
        // slowly rotate the cube:
        cube.rotation.x += 0.01;
        cube.rotation.y += 0.01;
        // rotate the icosahedron a little faster in the opposite direction:
        icoMesh.rotation.z += -0.03
        icoMesh.rotation.y += -0.03

            renderer.render( scene, camera );
        }
        

You must call the animate() function in order to tell the browser to use it:

animate();
        

Congratulations! Your scene should now look something like this:

three.JS object screenshot

Three.JS Helpers

Three.JS comes with a wide variety of scene helpers to assist in orienting the view of your scene.

We can add a Light Helper, which shows us where in the scene our light objects are positioned.

Let's add a light helper to our pointLight object:

// Helpers

        const lightHelper = new THREE.PointLightHelper(pointLight);

        scene.add(lightHelper)
        
light helper

We can also add a Grid Helper to show the 3D axes of our scene.

const gridHelper = new THREE.GridHelper(200,50);

        scene.add(gridHelper)
        

Orbit Controls

Three.JS has various types of Control methods to allow you to add interactive functionality to your scene.

Let's add an Orbit Control, which will allow us to move our camera and traverse our scene with mouse controls and zoom.

In order to add the Orbit Controls as a function, we need to import that package from the three.js dependencies. At the top of your document where you imported THREE.JS, (directly underneath import * as THREE from 'three';) add this line of code:

import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
        

Then, under your Helpers, activate your orbit controls by declaring it as a constant:

// Orbit Control

        const controls = new OrbitControls(camera, renderer.domElement)
        

Your orbit controls will not be functional until you add an .update() method to your new controls object WITHIN your animate(){} function:

function animate() {
            requestAnimationFrame( animate );
        // slowly rotate the cube:
        cube.rotation.x += 0.01;
        cube.rotation.y += 0.01;
        // rotate the icosahedron a little faster in the opposite direction:
        icoMesh.rotation.z += -0.03
        icoMesh.rotation.y += -0.03
        // ALLOWS YOUR ORBIT CONTROLS TO UPDATE LIVE IN REAL-TIME:
        controls.update()

            renderer.render( scene, camera );
        }
        

When you refresh your server, you should now be able to click and drag to orbit the camera view of your scene!

Scene Backgrounds

We can set our scene backgrounds to whatever we want, using regular image files.

night sky background photo
// Background

        const spaceTexture = new THREE.TextureLoader().load('images/night_sky.jpg')

        scene.background = spaceTexture;
        
background texture scene

Texture Mapping

In the world of 3D modeling, Texture Mapping refers to mapping flat images onto the surface of a 3D object. Three.JS allows us to do this by creating Texture objects and applying them to our 3D objects using methods.

Find an image file that you'd like to apply to the surface of a new object. We will be using this one:

smiley face texture

Save your texture image file in your /images directory.

Now, in our main.js, let's create a new texture that we will map onto a new object:

// Object texture mapping

        const smileTexture = new THREE.TextureLoader().load('images/smile.jpg')
        

Now, let's create a new Sphere object to map our new texture onto.

Remember new objects require three basic components:

// Object texture mapping

        const smileTexture = new THREE.TextureLoader().load('images/smile.jpg')

        const sphereGeometry = new THREE.SphereGeometry( 10, 22, 10 );

        const smileMaterial = new THREE.MeshBasicMaterial({map: smileTexture})

        const smileMesh = new THREE.Mesh(sphereGeometry, smileMaterial);

        scene.add(smileMesh);
        
new texture-mapped object

You can manipulate the object's static position, rotation, and size as you wish, the same as your other objects.

You can also add property changes to this new texture to be animated in real-time in your animate() function:

function animate() {
            requestAnimationFrame( animate );
        // slowly rotate the cube:
        cube.rotation.x += 0.01;
        cube.rotation.y += 0.01;
        // rotate the icosahedron a little faster in the opposite direction:
        icoMesh.rotation.z += -0.03
        icoMesh.rotation.y += -0.03
        // rotate the smiley sphere on the Y axis:
        smileMesh.rotation.y += 0.05
        controls.update()

            renderer.render( scene, camera );
        }
        

Normal Texture Mapping

Three.JS also allows you to create vivid textures to alter the surface shape of your mesh to create real, light-reactive textures on your objects.

To do this, we need to create a normal.

Normal mapping in the 3D world refers to a texture mapping technique used for faking the lighting of bumps and dents – an implementation of bump mapping. It is used to add details without using more polygons.

To create a "normal", you must first find a texture image you like that can be converted into a "normal".

We used this site to generate and customize a texture: Texture Generator Online

Here's the texture we generated:

texture

Now, we must convert this texture image file into a normal map. We used this site to upload our texture image file and have it converted into a normal: Normal Map Online

Here's the normal map that was generated from our image:

normal map

Now we need to create a new normalTexture object in our Javascript by loading a new texture and loading in our normalMap image file:

const normalTexture = new THREE.TextureLoader().load('images/normals/textureNormal.png');
        

Now, to apply our normal map to a new object, we must apply our normal map image to the normalMap property within our new MeshStandardMaterial.

// Normal Texture Map

        const torusGeo = new THREE.TorusKnotGeometry( 5, 1, 250, 5, 9, 15 );
        const torusMaterial = new THREE.MeshStandardMaterial( {
          normalMap: normalTexture,
          roughness: 0,
          metalness: .8
        } );

        const torusKnot = new THREE.Mesh( torusGeo, torusMaterial );

        scene.add( torusKnot );
        torusKnot.position.y = 20

        

We also added a roughness and metalness property value to make our new object look more shiny and reflect light. Your scene should look something like this:

normal mapping

How to Deploy your Three.JS App to GitHub Pages

Now comes the fun part.

To deploy your Three.JS app on GitHub Pages and display it to the world, we will use Vite to build a dist folder containing the finished site, and then publish that folder to a special gh-pages branch of your repository. GitHub serves your website from that branch.

You do not have to move files into dist by hand, and you do not have to change your import lines. Vite builds your JavaScript and the Three.JS library together into the dist folder for you.

Here are the steps you must follow:

Step 1: Configure Vite for building

Vite packs up your JavaScript and CSS on its own, but it has no way of knowing about files that your code only mentions as text, like 'images/night_sky.jpg'. We need to tell it to copy our asset folders into dist. In your project folder, next to package.json, create a file named vite.config.js and write this into it:

import { defineConfig } from 'vite'
import { cpSync, existsSync } from 'node:fs'

// Folders to copy into dist/ as-is. Add a name in this array whenever you add a new folder
// for other kinds of assets, like this:
// const copyDirs = ['images', 'models', 'data']
// For now, we just have images/ so this is sufficient:

const copyDirs = ['images']

export default defineConfig({
  base: './',          // relative URLs, so the site works at any GitHub Pages address
  publicDir: false,    // we are not using a public/ folder
  build: {
    outDir: 'dist',
  },
  plugins: [
    {
      name: 'copy-static-folders',
      apply: 'build',  // only runs during `npm run build`
      closeBundle() {
        for (const dir of copyDirs) {
          if (existsSync(dir)) cpSync(dir, `dist/${dir}`, { recursive: true })
        }
      },
    },
  ],
})

What this does: closeBundle is a moment Vite gives us to run our own code when the build is done, and ours copies each folder named in copyDirs into dist. If you add a new folder of assets (for example, 3D models), add its name to that list. If a folder is missing from dist after a build, check the spelling in copyDirs first.

Step 2: Build and preview

Make sure you are at the root of your project folder, then run:

npm run build

You should see a new dist folder containing a built index.html, an assets folder with your packed-up JavaScript and CSS, and a copy of your images folder. Now look at it in your browser:

npm run preview

Preview runs on a different port than npm run dev (often 4173), and it shows what is in dist, which is what will be published. Check that your background and textures appear. If they show up in the dev server but not in the preview, the problem is in this build step. Stop the preview with CTRL + C when you are done.

Step 3: Install gh-pages and add a deploy script

Make sure you are at the root of your project directory. Run the following command to install gh-pages:

npm install -D gh-pages

gh-pages is an npm package that publishes a folder to a branch named "gh-pages" on GitHub. You may read more about it here: https://www.npmjs.com/package/gh-pages

Then, within your package.json file, add a "deploy" line inside your "scripts". Remember to add a comma after the line before it:

"scripts": {
  "dev": "vite",
  "build": "vite build",
  "preview": "vite preview",
  "deploy": "gh-pages -d dist"
},

Step 4: Add your .gitignore

Before you use Git for the first time in this project, create a file in your project folder named exactly .gitignore (no .txt at the end, and create it from your code editor rather than the file manager). Copy the contents from Dr. B's .gitignore , or paste this:

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
# dist is intentionally NOT ignored, to make it easier to inspect build output in your repo
# If it's annoying you to see it in your main branch, go ahead and uncomment the dist line:
# dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

The most important line is node_modules, which keeps thousands of installed files out of your repository. Do this before your first git add .. Because we are keeping dist in the repository in this class, remember to run npm run build before you commit, so that the committed dist folder matches your code.

Step 5: Create a GitHub repository

Go to GitHub.com and create a new repository.

Fill in your repository name and a short description. Tip: try naming your remote repo the same as what you named your Three.JS project locally.

You do not need to add a readme, a gitignore, or a license.

new repo screen on github.com

Hit "Create Repository".

Once you get to this page, STOP! Keep this page open in your browser. We need to go back into your command line terminal to push your project code into this new remote repository.

github remote repo setup screen

Step 6: Push your code

Run npm run build one more time so that dist is up to date, then enter the following commands in your Command Line Terminal, one after the other:

git init
git branch -M main
git remote add origin "YOUR_REPOSITORY_URL"
git add .
git commit -m "YOUR_COMMIT_MESSAGE"
git push -u origin main

(The git branch -M main line makes sure your branch is named main, which is what GitHub expects.)

Step 7: Deploy

Make sure you are at the root of your project directory, and that you have run npm run build since your last changes. Then run this command in your terminal:

npm run deploy

This runs the "deploy" script that you added in Step 3. It creates the gh-pages branch on GitHub for you if it doesn't exist yet, and publishes the contents of dist to it.

npm run deploy gh-pages

Once your terminal says "Published", you need to wait a few minutes for your GitHub Pages site to be ready for viewing.

After a few minutes, go to the "Pages" tab in your repository settings. If it asks for a source, choose the gh-pages branch and the / (root) folder. Your website link will be there.

Updating your site later: make your changes, run npm run build, commit and push as usual, and run npm run deploy again.

Troubleshooting: If npm run deploy fails

If you see an error like Failed to get remote.origin.url, first check that your project knows about your GitHub repository:

git remote -v

You should see your repository's address listed twice, once for fetch and once for push. If the list is empty, go back to Step 6 and run the git remote add origin command.

If your repository is listed and you still get the error, the problem may be that Git itself can't run when npm starts it (this can happen on a Mac when an older Intel version of Git is installed). Test it with:

npm exec -- git --version

If that prints a version number, Git is fine and you should ask for help with the full error message. If it prints an error such as Bad CPU type in executable, ask your instructor for help fixing your Git installation!