Jorge Martinez

Aerospace Engineer and Senior Software Developer


Running Threejs in my website

Jorge Martínez Garrido

January 13, 2024

hugo three.js


In this tutorial, I am going to show you how to use Three.js in your Hugo website. By the end of this tutorial, you should be able to get interactive scenes like this one:

Use your mouse rotate around previous scene. Use the scrolling wheel to zoom in or out.

Main objective

The main objectives are:

Avoiding to install Three.js

It is possible to install Three.js by using a Content Delivery Network (CDN). These are servers hosting different files. In this case, these files are the ones for Three.js. From the official installation guidelines, it looks like https://unpkg.com/ is the desired CDN.

Thus, the first lines to include in our Markdown post should be:

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

At the time of this post, I am using version 0.160.0.

The three/addons/ import allows to use popular utilities like OrbitControls, which allows for an orbit camera within the scene.

Creating a placeholder for a scene

Any Three.js scene needs an element to render. A canvas for drawing. This element is usually a <div> with some id. This identifier is reference in the JavaScript code to indicate the renderer where it must present the scene.

Add the following lines right after previous installation script:

<div id="threejs-container-hello"></div>

Creating a simple scene

A simple scene is provided now. This code renders the wireframe of a cube.

import * as THREE from 'three';

const scene = new THREE.Scene();
scene.background = new THREE.Color(0xEEEEEE);

// Create a camera
const camera = new THREE.PerspectiveCamera(20, 4/3, 0.1, 1000);
camera.position.z = 5;

// Create a renderer
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('threejs-container-hello').appendChild(renderer.domElement);

// Create a cube
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x000000, wireframe: true});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

// Add animation
const animate = () => {
    requestAnimationFrame(animate);

    // Rotate the cube
    cube.rotation.x += 0.01;
    cube.rotation.y += 0.01;

    renderer.render(scene, camera);
};

// Create an animation loop
const animate_wired_cube = () => {
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
};

animate_wired_cube();

Pay special attention to the following line in previous code:

document.getElementById('threejs-container').appendChild(renderer.domElement);

Note this is indicating the renderer to look for the previous placeholder we created.

Also, pay attention to the import statements. Since we are using CDNs, these are slightly different than the ones required when using Node modules.

Putting all together by creating a Hugo shortcode

Hugo shortcodes are amazing! These are HTML files that support templating. Pass arguments and generate dynamically your desired HTML content.

Thus, we can combine all previous codes into a shortcode named threejs.html. Store this shortcode in your layouts/shortcodes/ directory:

<!-- Three.js short code for linking JS code and rendering a scene -->

{{ $version := .Get "version" }}
{{ $id := .Get "id" }}
{{ $src := .Get "src" }}

<!-- Create a container for rendering the scene -->
<div id="threejs-container-{{ $id }}"></div>

<!-- Import Three.js -->
<script type="importmap">
  {
    "imports": {
      "three": "https://unpkg.com/three@{{ $version }}/build/three.module.js",
      "three/addons/": "https://unpkg.com/three@{{ $version }}/examples/jsm/"
    }
  }
</script>

<!-- Load the script -->
<script type="module" src="{{ $src }}"></script>

Note that this accepts three arguments:

For our example, use it as:

{{< threejs version="0.160.0" id="hello" src="hello.js" >}}

Which results in: