Three Js example
How it works
The smallest Three.js setup in the framework: a scene, a camera, a renderer and a cube that spins.
The class in src/js/handler/three is the starting point for any WebGL component built with Three.js (three on npm). It creates a scene, a perspective camera and a WebGLRenderer sized to the element, appends the renderer's canvas to the element and adds one mesh: a 1×1×1 box with a MeshNormalMaterial, which colours every face by the direction it points to and needs no lights. A requestAnimationFrame loop turns the box by 0.01 radians per frame on x and y and renders.
Everything lives in ThreeGL.js; the handler only names the hook. The renderer is created with antialiasing and a transparent background, so the page shows through around the cube, and the pixel ratio is capped at 2 so a retina screen does not quadruple the pixels drawn. On window resize the camera aspect and the renderer size follow the element.
The markup
One element with the js--three-webgl hook. It must have a height: the canvas takes the element's clientWidth and clientHeight, so an element with no height draws nothing. The example sizes it with an inline style to keep the snippet short; in a project give the element a component class and set the size in SCSS.
<!-- The element is the frame; the class appends a canvas that fills it -->
<div class="js--three-webgl" style="width: 100%; height: 400px;"></div> The handler
Handler.js has nothing to pass on: the config is the element and nothing else, since the class has no options.
this.config = ({ element }) => ({
element,
}); This is the whole setup, in ThreeGL.js. The numbers to change are here: the field of view and distance of the camera, the geometry and the material:
init() {
this._setSize();
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(75, this.width / this.height, 0.1, 1000);
this.camera.position.z = 3;
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
this.renderer.setSize(this.width, this.height);
this.DOM.element.appendChild(this.renderer.domElement);
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshNormalMaterial();
this.mesh = new THREE.Mesh(geometry, material);
this.scene.add(this.mesh);
this.animate();
} What the framework does for you
- Lazy loading. An element is only built when it gets within 30px of the viewport (Boostify scroll).
ThreeGL.js, and with itthree, is imported through the Manager on first use, so a page without the hook never downloads the library. - Page transitions. Before swup replaces the content every instance is destroyed: the frame loop is cancelled, the resize listener removed, the geometry and material disposed, the scene cleared, the renderer disposed and its canvas taken out of the DOM. The new page builds its own.
- One renderer per instance. Every element gets its own
WebGLRenderer, which is its own WebGL context. Browsers allow a limited number of live contexts, so keep the number of hooks on a page small, and rely on the destroy above to free them on every transition.
if (!this.DOM?.element) return;
cancelAnimationFrame(this.animationId);
window.removeEventListener("resize", this.onResize);
// dispose geometry and material explicitly (scene.clear() does not dispose)
if (this.mesh) {
if (this.mesh.geometry) this.mesh.geometry.dispose();
if (this.mesh.material) {
if (Array.isArray(this.mesh.material)) {
this.mesh.material.forEach((m) => m.dispose && m.dispose());
} else {
this.mesh.material.dispose && this.mesh.material.dispose();
}
}
}
if (this.scene) this.scene.clear();
if (this.renderer) {
this.renderer.dispose();
this.renderer.domElement?.remove();
} Cube
A box with MeshNormalMaterial, turning on two axes.
The only example: the hook on an element 400px high. The cube sits 3 units in front of a 75° camera and never stops turning; there is no interaction, no lights and no texture to load, which is why the canvas is drawn on the first frame.
<!-- The element is the frame; the class appends a canvas that fills it -->
<div class="js--three-webgl" style="width: 100%; height: 400px;"></div> Options reference
No attributes: the hook is the only thing the HTML carries. What can change is in the class.
There are no data-* attributes and the handler passes no options. The size comes from the element, and everything else is a value in ThreeGL.js:
| In the class | Value | What it does |
|---|---|---|
PerspectiveCamera(75, …, 0.1, 1000) | fov 75, near 0.1, far 1000 | The field of view and the clipping planes of the camera |
camera.position.z | 3 | How far the camera is from the cube; higher means a smaller cube |
BoxGeometry(1, 1, 1) | 1×1×1 units | The shape. Any Three.js geometry works in its place |
MeshNormalMaterial | no options | Colours by face normal, needs no lights. Another material needs lights in the scene |
rotation.x += 0.01, rotation.y += 0.01 | radians per frame | The turning speed on each axis, in animate |
setPixelRatio(min(devicePixelRatio, 2)) | cap 2 | The sharpness on high-density screens, and the cost of each frame |
The size of the canvas is the size of the element: set the width and height, or an aspect ratio, in SCSS on a component class of your own; js--three-webgl is a hook and carries no styles.