Page 5: Three.js preliminaries

Spring 2026 Sample Solution

On this page, we’ll just deal with the mechanics of using the THREE.js library, and some of the issues specific to using the library in the way we’ve been programming in class.

There are a few reasons why Three is different from using the APIs we’ve used so far…

  1. Three is an external library that we need to load in as a JavaScript module (in contrast with using built-in APIs). This will force us to confront how JavaScript deals with modules, and how the browser can help us load them from different places.
  2. Three uses a variety of JavaScript programming conventions that might seem a little weird at first. But once you get used to them, they make programming using a complex API much easier.
  3. Three is a pretty big API with lots of features and parts. While we want you to focus on learning the graphics concepts, you will need to deal with the API in order to practice them.

Readings About Three.JS

Over the course of this assignment, you will probably need to read some of the THREE.js Documentation (current). When you wonder what you can do with a class like BufferGeometry, or what a function like DataTextureLoader.load does, this is the place to look.

Learning THREE: The official documentation can be found here. This is mainly a reference. Once you get started, you can look up the details of the things you need.

A Note on THREE versions

In class, we use the same version of THREE for the whole semester to avoid switching versions in the middle. That way the workbooks all use the same, consistent version.

For Spring 2026, we chose version r180. This is already not the most recent release (you can see what changed on the THREE Releases Page). The differences probably do not matter for class.

Be warned: a lot of the fancier stuff may not make sense until we’ve talked about what is happening “under the hood”. The documentation dives into details quite quickly. The workbook is designed to guide you through the basics. We suggest you use the official documentation as a reference to get the details as you need them.

The workbook and lectures should give you enough of an idea of how THREE works to complete the workbook, but you might find it helpful to see other materials or examples. The workbook’s version of the library does not include THREE.js in its entirety, mostly excluding many of the examples. We only included enough to get you through the workbooks. You can download the entire library if you wish, but please don’t add it to your git repo if you do.

While there are a lot of THREE resources around the web, and the API documentation is pretty complete, things tend not to be that well-organized or beginner-friendly. If you find good additional resources, please post them on Piazza. Here are some things I found useful (if you find others, post them on Piazza!):

  • THREE.js Getting Started - Shows you the minimal steps for creating a scene with THREE. It’s a bit redundant with this workbook, but seeing things in someone else’s words may be useful.

  • THREE.js Tutorial - This is very similar to the way we’ll introduce things in this workbook. Read this to see our topics explained in different words.

  • Aviator Game - This is a complicated example that builds things up from scratch. It’s a bit of a fancy application to start with, but you can see how the author builds something pretty cool using the pieces we’ll learn about.

  • Discover ThreeJS - This is an online textbook that walks through learning THREE, and does a nice job of connecting to the graphics background (in places). In particular, the the threejs built-in geometry tutorial (with train) talks about using the built-in geometries, and the threejs transformations tutorial discusses transformations.

Accessing Three.js

Here is the spinning cube again:

On this page, we’ll focus on some of the “non-graphics” aspects of the program. The program is “eg-three-check.js” (look at it in VS Code, not in the browser) if you want to see the whole thing. We’ll walk through it line by line.

The first thing we need to consider is how we “load” and refer to three.

import * as T from "three";

This statement imports three into the namespace of our program as the symbol T. For the rest of the file, I can refer to T to access the three library. You can think of T like an object (with methods and member variables), but it is really a special kind of thing (a module) that just happens to use similar syntax to an object. I could have used any symbol I wanted. I chose T since it is compact. This was very important when I wanted to project code on a small screen. Sometimes, I prefer to use a more descriptive symbol.

The * is a wildcard. It means we want to import everything from the module. The T symbol has access to everything.

The "three" tells the import what to load, and where to load it from. However, you might notice that it doesn’t give much information. Typically, the “from” target gives a complete specification, but here we just give the “bare” name. (“Bare” is a technical term: JavaScript calls this a “bare” specifier.) With a bare specifier, something else is responsible for resolving what it is. In our case, we will use the web browser: the HTML file that loads this JavaScript file will set things up using a technique called importmap.

If you look at the HTML file that loads the JavaScript (it’s multiplepages/eg-three-check.html ), which you might prefer to open in your IDE, you will see the lines:

<script type="importmap">
      {
          "imports": {
              "three": "https://unpkg.com/three@0.180.0/build/three.module.js",
              "three/": "https://unpkg.com/three@0.180.0/"
         }
      }
</script>

The actual importmap includes some other things as well - but I want to focus on three.

What this tells the browser is that when a JavaScript program asks for a module named “three”, it should access it from the path https://unpkg.com/three@0.180.0/build/three.module.js. This fetches three from the web (using the unpkg content delivery network). The first line “three” gives us the main library file. The second line “three/” allows us to access other parts of the library if we need them.

Internally, three requires importmaps so it can refer to its different pieces. See Using Import Maps for more details.

There are some catches here. One is that our JavaScript program only knows what and where three is because the HTML page that loads it tells us. That means the program will only work if it is loaded by something that provides that information. For example, if you load the JavaScript file into VSCode, VSCode won’t know what “three” means unless we tell it, so you won’t get hints and type checking. To tell it, we provide VSCode with a “jsconfig.json” file (we’ve included it as part of the workbook). This way we can use the type information that three provides so we can use the TypeScript type checker (see Typed JavaScript and CS559). We’ve included the type information as part of the workbook.

THREE and HTML

THREE actually draws on a Canvas element. HTML Canvas elements provide multiple APIs to allow drawing on them in different ways. In earlier workbooks, we used getContext to get the 2D Canvas API. Internally three will access the appropriate “context” (or API) to draw on it in 3D.

By default, THREE will create the canvas element for us. If it does, we need to put it on the web page. We can do this using the following code:

15
16
17
let renderer = new T.WebGLRenderer();
renderer.setSize(200, 200);
document.getElementById("div1").appendChild(renderer.domElement);

This creates a WebGLRenderer object, which has a property that is an HTMLCanvasElement (where the drawing will happen). The call to setSize sets the size of the Canvas (in HTML pixels). The last line finds a container element called three1 (it’s a <div>) that we put into the HTML where we want to put the newly created canvas and inserts it into the container.

The WebGLRenderer object is the object that will take care of drawing for us. We’ll see it more later.

If we had a Canvas already, we could tell THREE to use it instead. So here is a Canvas in HTML:

04-05-01   view Kinds of boxes:
view - look at the box and experiment with it
examine - look at the code for the box
edit - change the box's content
rubric - several steps are suggested in the rubric page - form elements on page in rubric
   04-05-01.html   04-05-01.js

And the code looks like:

18
19
20
21
22
23
let canvas = document.getElementById("canvas1");
if (!(canvas instanceof HTMLCanvasElement))
  throw new Error("Canvas is not HTML Element");

// Set up the renderer, which will create the Canvas for us
let renderer = new T.WebGLRenderer({ canvas: canvas });

Basically, we have to find the Canvas and pass it to the constructor of the renderer. Notice how we check to make sure the canvas really is a Canvas. We have to do this because it could be any other type of element or even undefined if no such element exists on the webpage.

You can view the full code in 04-05-01.js (04-05-01.html).

Parameter Passing

There is one subtle thing here that is a common paradigm in THREE programming (in fact, in JavaScript programming in general). Note how we pass the parameters to the WebGLRenderer constructor as an object {"canvas":canvas}. Since JavaScript doesn’t support keyword arguments (like Python), we need some mechanism for specifying the parameters we want, without having to include the parameters that should take default values. So instead of passing a list of parameters, we pass an object that contains all of the parameters we want to control.

For example, suppose we have a function like:

function foo(a = 1, b = 2, c = 3, d = 4) {
  // do something with a, b, c and d
}

and we want to call it with the default parameters for everything except d. In this case, we’re stuck. If we wanted d=7, we would need to write foo(1,2,3,7). If this were Python, we could write foo(d=7), but it’s not. So instead we define the function to take one object that holds the parameters:

function foo(object) {
  let a = "a" in object ? object.a : 1;
  let b = "b" in object ? object.b : 2;
  let c = "c" in object ? object.c : 3;
  let d = "d" in object ? object.d : 4;
  // do something with a, b, c and d
}

so we can write foo({d:7}) (remember JavaScript quoting rules; we don’t need quotes around d). This is a little bit of a hassle when we create the function, but very convenient when we use it (especially for functions with lots of arguments). Fortunately, the authors of THREE wrote a lot of their code this way.

Traditional Object-Oriented Programming

Three makes use of the “traditional” style of object-oriented programming in JavaScript. It uses classes, constructors, and inheritance. This style of object-oriented programming should feel familiar if you’ve learned some other language first.

It is worth reading a bit about how object-oriented programming works in JavaScript, even if you are just going to use the “traditional” approach. JavaScript is extremely flexible, so even if you are just doing simple things (like single inheritance), you need to specify some things explicitly.

We have a two-part tutorial on “Traditional Object Oriented Programming in JavaScript”: (part 1) and (part 2).

Summary: Using THREE

So now, you can hopefully create a THREE program with the THREE library. Now let’s actually try to make stuff with it.

Next: Page  6 - Basics of Three.js Programming