Integrating DOM Elements as 3D Objects in Three.js
HTML elements can be transformed into spatial objects, behaving similarly to standard Three.js meshes or sprites within a 3D environment. The fundamental distinction between a standard mesh and a sprite during scene rotation is that a sprite's rectangular plane continuously faces the camera, remaining parallel to the screen, whereas a mesh adjusts its orientation based on the scene's transformation.
The CSS3DRenderer Extension
Three.js provides the CSS3DRenderer.js module, which exports three primary classes: CSS3DRenderer, CSS3DObject, and CSS3DSprite.
import { CSS3DRenderer, CSS3DObject, CSS3DSprite } from 'three/addons/renderers/CSS3DRenderer.js';Understanding CSS3DRenderer
While functionally similar to WebGLRenderer, CSS3DRenderer does not utilize WebGL APIs. Instead, it leverages CSS3 transformations to position and render HTML elements in 3D space. It shares standard methods like .setSize() and .render(), but lacks WebGL-specific features such as .setClearColor().
const cssRenderer = new CSS3DRenderer();
cssRenderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('viewport').appendChild(cssRenderer.domElement);
// ... within the animation loop
cssRenderer.render(scene, camera);CSS3DObject vs. CSS3DSprite
The CSS3DRenderer cannot render standard Three.js geometries like Mesh, Sprite, or Line. It exclusively renders CSS3DObject and CSS3DSprite instances. A CSS3DObject behaves like a planar mesh that rotates within the 3D scene, while a CSS3DSprite mimics the behavior of a standard sprite, always facing the camera.
function createLabel(targetMesh, elementId) {
const domElement = document.getElementById(elementId);
const cssObj = new CSS3DObject(domElement);
// Alternatively: const cssSprite = new CSS3DSprite(domElement);
scene.add(cssObj);
cssObj.position.copy(targetMesh.position);
cssObj.position.y += 60;
cssObj.scale.set(0.4, 0.4, 0.4);
}Combining WebGL and CSS3D Renderers
A common architecture involves running both renderers simultaneously. WebGLRenderer handles the 3D models, while CSS3DRenderer overlays interactive HTML interfaces or data annotations, frequently used in IoT digital twin applications. To layer them correctly, the WebGL canvas must be positioned absolutely so it doesn't obstruct the CSS3D elements.
const glRenderer = new THREE.WebGLRenderer();
glRenderer.setSize(window.innerWidth, window.innerHeight);
glRenderer.setClearColor(0x1a1a1a, 1);
glRenderer.domElement.style.position = 'absolute';
document.body.appendChild(glRenderer.domElement);
const cssRenderer = new THREE.CSS3DRenderer();
cssRenderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(cssRenderer.domElement);
function animate() {
requestAnimationFrame(animate);
glRenderer.render(scene, camera);
cssRenderer.render(scene, camera);
}When implementing camera controls, OrbitControls must be attached to the WebGL renderer's DOM element. This ensures that the CSS3D elements transform seamlessly alongside the WebGL scene during rotations and scaling.
const controls = new OrbitControls(camera, glRenderer.domElement);