Overlay HTML Labels on Three.js 3D Objects
This article explains how to use the CSS2DRenderer in
Three.js to overlay standard HTML elements as labels onto 3D objects.
You will learn what the CSS2DRenderer is, why it is a
powerful tool for web developers, and the step-by-step implementation
process to make 2D HTML elements seamlessly track 3D coordinates in your
WebGL scene.
What is CSS2DRenderer?
The CSS2DRenderer is an official Three.js add-on that
allows you to combine standard HTML/CSS elements with a 3D WebGL
scene.
By default, rendering crisp text in WebGL requires complex techniques
like canvas textures or Signed Distance Field (SDF) fonts. The
CSS2DRenderer bypasses this complexity by rendering your
HTML elements in a separate DOM layer positioned directly above the
WebGL canvas. It translates the 3D coordinates of your objects into 2D
screen coordinates, allowing HTML elements to follow 3D meshes as the
camera moves, rotates, and zooms.
Step-by-Step Implementation
To overlay HTML labels onto 3D objects, you need to set up the
CSS2DRenderer alongside your standard WebGL renderer.
1. Import the Required Modules
First, import the renderer and the 2D object wrapper from the Three.js library:
import * as THREE from 'three';
import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';2. Set Up the CSS2DRenderer
Create an instance of the CSS2DRenderer, style its DOM
element to overlay perfectly on top of your WebGL canvas, and append it
to the document body.
// Initialize WebGL Renderer
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Initialize CSS2D Renderer
const labelRenderer = new CSS2DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0px';
labelRenderer.domElement.style.pointerEvents = 'none'; // Allows clicking through labels to control the 3D scene
document.body.appendChild(labelRenderer.domElement);3. Create the HTML Label and Wrap It
Create a standard HTML element using vanilla JavaScript, style it
with CSS, and wrap it in a CSS2DObject.
// Create HTML element
const labelDiv = document.createElement('div');
labelDiv.className = 'label';
labelDiv.textContent = 'Earth';
labelDiv.style.color = '#ffffff';
labelDiv.style.backgroundColor = 'rgba(0, 0, 0, 0.6)';
labelDiv.style.padding = '5px 10px';
labelDiv.style.borderRadius = '5px';
labelDiv.style.fontFamily = 'sans-serif';
// Wrap the element in a CSS2DObject
const labelObject = new CSS2DObject(labelDiv);
labelObject.position.set(0, 1.5, 0); // Position the label slightly above the object's center4. Attach the Label to a 3D Object
Add the CSS2DObject as a child of the target 3D mesh.
Because it is a child of the mesh, it will automatically move and rotate
with the mesh.
// Create a 3D object (e.g., a sphere)
const geometry = new THREE.SphereGeometry(1, 32, 32);
const material = new THREE.MeshBasicMaterial({ color: 0x0000ff });
const earthMesh = new THREE.Mesh(geometry, material);
scene.add(earthMesh);
// Attach the label to the mesh
earthMesh.add(labelObject);5. Render Both Scenes in the Animation Loop
In your animation loop, you must render both the standard WebGL scene and the CSS2D scene.
function animate() {
requestAnimationFrame(animate);
// Rotate mesh to demonstrate the label tracking
earthMesh.rotation.y += 0.01;
// Render WebGL
renderer.render(scene, camera);
// Render HTML Labels
labelRenderer.render(scene, camera);
}
animate();6. Handle Window Resizing
Ensure both renderers resize correctly when the browser window dimensions change.
window.addEventListener('resize', onWindowResize);
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.setSize(window.innerWidth, window.innerHeight);
}