ThreeJS Hello Cubes JavaScript Tutorial

This article covers how to create a 3D Web app with JavaScript using ThreeJS.

The app will display several 3D rotating cubes on the screen.

Step 1. Define the project

The project will be called threejs-hello and use the following components:

Folders

  • src - a source folder to hold all the files and subfolders
    • the root of the Web app will be src
  • src/css - a stylesheet folder that will be a subfolder of src

Browser files

  • src/index.html - the main browser file to display the scene
  • src/css/app.css - the stylesheet for the Web app

Application files

  • src/app.js - the main JavaScript file of the Web app
  • src/cube.js - a cube factory class that will create new ThreeJS cubes on demand
  • src/cube-scene.js - a cube scene factory class that will return a new ThreeJS scene containing a number of rotating cubes

Step 2. Create a new project folder

Now that you’ve defined the project structure, it’s time to set it up:

  1. Open up a terminal / command window
  2. Create a new folder called threejs-hello
  3. Switch to that folder
  4. Under that folder, create a new subfolder called src
  5. Under the src folder create a new subfolder called css

Step 3. Create the project files

  1. In the src folder create the following empty files:
    index.html
    app.js
    cube.js
    cube-scene.js
    
  2. In the src/css folder create the following empty file:
    app.css
    

Step 4. Define the index.html file

Paste the following into the src/index.html file:

<html>
  <head>
    <title>threejs-hello</title>
  <link rel="stylesheet" href="./css/app.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r122/three.min.js"></script>
  </head>
  <body>
    <script type="module" src="./app.js"></script>
  </body>
</html>

Code Description

The code above, does the folowing:

  • Sets the title that appears at the top of the browser window
    • You can make this anything you want
  • Includes the css/app.css style sheet that you will define soon
  • Includes the minimized ThreeJS library JavaScript file using a CDN network
    • The /r22/ designation refers to the build version number
  • Includes the src/app.js file that you will define soon
    • It’s important to use the type="module" attribute or the code won’t load properly

Step 5. Fill in the src/css/app.css file

Paste the following into the src/css/app.css file:

body {
  margin: 0;
}

canvas {
  width: 100%;
  height: 100%;
}

Code Description

The css code above does the following:

  • Sets all margins on the body to zero
  • Sets the width and height of a canvas element created by the script to 100%

The settings are to allow a 3D scene to fill the entire browser window.

Step 6. Define the cube.js file

Paste the following info the src/cube.js file:

// 1. Define and export a cube factory class
export class CubeFactory {

  // 2. Define a static create method to return new cubes
  static create( spec = {} ) {

    // 3. Setup default values or use arguments
    let {
      name = "cube", 
      color = "#FF00FF",
      width = 1,
      height = 1,
      depth = 1,
      x = 0.0,
      y = 0.0,
      z = 0.0,
    } = spec;

    // 4. Use ThreeJS to define a 3D cube
    var geometry = new THREE.BoxGeometry(width, height, depth);
    var material = new THREE.MeshBasicMaterial({ color: color });
    var cube = new THREE.Mesh(geometry, material);

    // 5. Use the name property to specify a type
    cube.name = name;

    // 6. Using ThreeJS methods on the cube, move it to a specific offset
    cube.translateX(x);
    cube.translateY(y);
    cube.translateZ(z);

    // 7. Return the new cube
    return cube;
  }
}

Code Description

The code above does the following:

  1. Defines and exports a cube factory class (CubeFactory)
  2. Defines a static create method to return new cubes
    • The method takes an optional object (spec) defining override parameters
  3. Setup default values or use arguments
    • If no spec object is passed to the method, default values will be used
    • If the spec object contains existing parameters, the default values will be overwritten for that parameter
    • Values for color, size (width, height, depth) and offset (translate[X,Y,Z]) can all be overridden
  4. Uses ThreeJS to define a 3D cube
    • One way to create a 3D object in ThreeJS is to combine a geometry and a material to define a mesh
  5. Use the name property to specify a type
    • Instead of assigning individual names to each cube, the name property will be used to designate a type (in this case “cube”)
  6. Use ThreeJS methods on the cube to move it to a specific offset from 0, 0, 0
    • The default values are all set to 0.0, so the cube would be drawn in the 3D center of the scene
  7. Returns the new cube

Usage example

In this example, cubes of various colors, sizes and locations would be added to a scene:

var cubeOptions = [
  { color: "#FF0000" },
  { color: "#00FF00", width: 0.5, height: 2, depth: 0.5 },
  { color: "#0000FF", width: 2, height: 0.5, depth: 0.5 },
  { color: "#FF00FF", width: 0.5, height: 0.5, depth: 2 },
  { color: "#FFFF00", translateX: 3.0 },
  { color: "#FF6619", translateX: -3.0 },
  { color: "#AAAAAA", translateY: 2.0, translateZ: -0.05, width: 0.5, height: 0.5, depth: 2 },
  { color: "#04D9FF", translateY: -2.0, translateZ: 0.05, width: 0.5, height: 0.5, depth: 2 },
];

cubeOptions.forEach(options => scene.add(CubeFactory.create(options)));

Step 7. Define the cube-scene.js file

Paste the following info the src/cube-scene.js file:

// 1. Import the cube class factory
import {CubeFactory} from './cube.js';

// 2. Define and export a scene factory class
export class CubeSceneFactory {

  // 3. Define a static create method to return new scenes filled with cubes
  static create( spec = {}) {

    // 4. Setup default values or use spec arguments
    let {
      clear = "#000000",
      // fov — Camera frustum vertical field of view
      fov = 75,
      // aspect — Camera frustum aspect ratio
      aspectRatio = window.innerWidth / window.innerHeight,
      near = 0.1,  // near — Camera frustum near plane
      far = 1000, // far — Camera frustum far plane.
    } = spec;

    // 5. Define a list of options for cubes to be created
    var cubeOptions = [
      { color: "#FF0000" },
      { color: "#00FF00", width: 0.5, height: 2.0, depth: 0.5 },
      { color: "#0000FF", width: 2.0, height: 0.5, depth: 0.5 },
      { color: "#FF00FF", width: 0.5, height: 0.5, depth: 2.0 },
      { color: "#FFFF00", x:  3.0 },
      { color: "#FF6619", x: -3.0 },
      { color: "#AAAAAA", y:  2.0, z: -0.05, width: 0.5, height: 0.5, depth: 2 },
      { color: "#04D9FF", y: -2.0, z:  0.05, width: 0.5, height: 0.5, depth: 2 },
    ];

    // 6. Setup a ThreeJS renderer to render the scene
    var renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setClearColor(clear);
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    // 7. Create a new ThreeJS scene
    var scene = new THREE.Scene();

    // 8. Create a ThreeJS camera
    var camera = new THREE.PerspectiveCamera(fov, aspectRatio, near, far);
    camera.position.z = 5; // Set camera position

    // 9. Add several cubes to the scene using the cubeOptions list of parameters
    cubeOptions.forEach(options => scene.add(CubeFactory.create(options)));

    // 10. Define a cube scene with methods to return
    var cubeScene = {

      // 11. Define a method on the cube scene to handle browser window resizing
      resize: function () {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
      },

      // 12. Define a method to be called when the scene is rendered
      step: function () {
        scene.traverse(function (cube) {
          if (cube.name === "cube") {
            cube.rotation.x += 0.01;
            cube.rotation.y += 0.02;
          }
        });

        renderer.render(scene, camera);
      }
    };

    // 13. Return the new cube scene
    return cubeScene;
  }
}

Code Description

The code above does the following:

  1. Imports the cube class factory (CubeFactory)
  2. Defines and exporst a scene factory class (CubeSceneFactory)
  3. Defines a static create method to return new scenes filled with 3D cubes
  4. Sets up default values or uses spec arguments
  5. Defines a list of options for cubes to be created
  6. Sets up a ThreeJS renderer to render the scene
  7. Creates a new ThreeJS scene
  8. Creates a ThreeJS camera
  9. Adds several cubes to the scene using the cubeOptions list of parameters
  10. Defines a cube scene with methods to return
  11. Defines a method on the cube scene to handle browser window resizing
  12. Defines a method to be called each time the scene is rendered
    • Traverses the scene looking for all objects with a name of “cube”
    • If a cube object is found it’s x and y rotation parameters are incremented to animate the cube spinning
  13. Returns the new cube scene

Step 8. Define the app.js file

The main app.js file will do the following:

  • Import the CubeSceneFactory class
  • Create a new cube scene (cubeScene) using the factory
  • Define a render function to handle drawing a frame of the scene
  • Add event listener code to handle browser window resizing

Paste the following into the src/app.js file:

// 1. Add reminder for how to reference the script
/*
  In a browser must use script type="module" parameter:

  <script type="module" src="./src/app.js"></script>
 */

// 2. Import the cube scene factory
import {CubeSceneFactory} from './cube-scene.js';

// 3. Create a new cube scene using the factory
var cubeScene = CubeSceneFactory.create({
  clear: "#111111"
});

// 4. Define a render function
var render = function() {
  requestAnimationFrame( render );
  cubeScene.step();
}

// 5. Add and define a listener for browser resize events
window.addEventListener( 'resize', onWindowResize, false );

function onWindowResize(){
    cubeScene.resize();
}

// 6. Call render to initiate the scene display
render();

Code Description

The code above does the following:

  1. Lists a reminder in the comments for how to include the script in an HTML file
  2. Imports a CubeSceneFactory from ./cube-scene.js which you will define soon
  3. Creates cubeScene from the factory using its create method
    • The clear RGB (color) hex value will be used as the background color
  4. Defines a render function that does the following:
    • calls the ThreeJS requestAnimationFrame function
    • calls a method on the cubeScene called step to rotate each cube a little for every frame
  5. Adds and defines a window listener to handle the browser being resized
    • Calls the cubeScene resize method to draw frames based on the changed browser size
  6. Calls render to initiate drawing the screen

Step 9. Serve the files

To run the app you need to serve up src/index.html using a Web server.

On a Mac you can do that with this command:

cd src
python -m SimpleHTTPServer $PORT || 8000

Then in a browser like Chrome, browse to:

  • http://localhost:8000

On a Mac you can also open the browser from the command line”

open http://localhost:8000

You can also host demo apps for free by posting them to services like:

If you have an AWS, Azure or Google Cloud account you can also serve the files from buckets as a static Web site.

Troubleshooting

  • This demo works best in the latest version of Chrome or the Oculus Browser
  • If you are getting a blank screen, use the browser JavaScript console to review issues

Conclusion

In this article you learned how to:

  • Create a simple Hello World app using ThreeJS
  • Define a class factory to generate cubes of various colors, sizes and positions
  • Define another class factory to generate a ThreeJS scene filled with cube
  • Use a render loop to animate cubes on the screen
  • Serve the files so the 3D app can be viewed in a browser

References



About the Author

Mitch Allen has worked for software companies in Silicon Valley, along Boston’s Route 128 and in New York’s Silicon Alley. He currently works for a robotics company in Massachusetts.