
Check out an example: Three.js Blog
See other awesome things you can do with three.js: Three.JS examples page
Node.js and
npm
at the command line.
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.

node -v
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.)
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!
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:
mkdir makes the folder, and cd moves you into it. This folder is
your project and will become your git repo.npm init -y creates a package.json file, which is your project's
list of settings and dependencies.npm install -D vite installs Vite as a development tool (the -D
means it helps you build the site, but is not code your site runs).You should now see package.json, package-lock.json, and a
node_modules folder in your project.
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.
node_modules or package-lock.json.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.
package.jsonOpen 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.
npm run dev shows your files as you work on them, with live reload.npm run build packs your site into a dist/ folder.npm run preview shows what is in dist/, which is what gets published.
Run npm run build again whenever you want the preview to show your latest changes.Note: The npm commands only work if you're at the root of your project folder with package.json inside.
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.
You'll need three basic elements in your HTML to render your 3D world:
<canvas> with a unique @id that will literally act as the canvas for
your 3D objects to live in.<script type="module" src="main.js"> to connect your HTML to
your Javascript file with all of the 3D instructions inside it.<link rel="stylesheet" href="style.css"> to connect your HTML
to your CSS file.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.
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.
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
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.
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';
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);
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.
() as arguments separated by commas: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 );
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;
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);
Point Lights are lights that only go in one direction.
Ambient lights act more like the sun, spreading light in all directions.
You can change the color of the light by manipulating the HEX code value of the new THREE.Light() object within the parentheses.
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 } );

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 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)

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)
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!
We can set our scene backgrounds to whatever we want, using regular image files.
create a new folder in your project and name it images
find a cool, high-resolution image that you'd like to appear as the background of your scene. We are using this one:

save the image file of your choosing inside your new /images directory.
File names matter once you publish. Use lowercase letters, no spaces, and refer to your
images with relative paths such as 'images/night_sky.jpg' (no leading slash). Your own
computer may forgive a capital letter that doesn't match, but the web server for your published site
will not, and the image will quietly fail to load.
create a new Texture object by referencing your image, and add it to the scene:
// Background
const spaceTexture = new THREE.TextureLoader().load('images/night_sky.jpg')
scene.background = spaceTexture;

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:

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:
geometrymaterial
BasicMaterial will have a value of
{map: smileTexture} instead of a color hex code.
mesh, which combines the geometry and material to create the
final
visible object.// 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);

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 );
}
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:

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:

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:

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:
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.
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.
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"
},
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.
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.

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.

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.)
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.
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.
npm run deploy failsIf 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!