Integrating 2D HTML DOM elements with a 3D Three.js scene is a common requirement for creating rich, interactive web experiences. This allows you to leverage the full power of HTML and CSS for UI elements while still having a dynamic 3D environment. There are several approaches to achieve this, each with its own advantages and limitations.
This is the simplest and most common method for displaying UI elements (like buttons, text, or forms) over your Three.js scene.
<canvas> element. You can then position HTML elements on top of this canvas using standard CSS positioning (e.g., position: absolute or position: fixed) and z-index properties.CSS2DRenderer and CSS3DRenderer)Three.js provides specialized renderers that allow you to integrate HTML DOM elements directly into your 3D scene, making them appear as if they are part of the 3D world.
CSS3DRenderer: Uses CSS 3D transforms to apply hierarchical 3D transformations to DOM elements. Ideal for creating 3D layouts with HTML content (e.g., information panels that rotate with 3D objects).CSS2DRenderer: Focuses on 2D HTML elements that always face the camera, regardless of the camera's rotation. Useful for labels, annotations, or UI elements that should always be readable.These renderers are powerful but have limitations, such as HTML elements not being occluded by WebGL objects.
For more fine-grained control or when you need HTML elements to precisely follow 3D objects without using the CSS renderers, you can manually convert 3D world coordinates to 2D screen coordinates.
left and top CSS properties of your HTML element to match these calculated screen coordinates.Vector3.project(camera) to convert to normalized device coordinates (NDC), map NDC to pixel coordinates, and apply to HTML element's CSS. Update this in your animation loop.This is a conceptual example of overlaying a simple HTML div on top of a Three.js canvas. The HTML element's position is fixed relative to the viewport.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTML Overlay</title>
<style>
body { margin: 0; overflow: hidden; }
#threejs-container { position: relative; width: 100vw; height: 100vh; }
canvas { display: block; }
#overlay-text {
position: absolute;
top: 20px;
left: 20px;
color: white;
font-family: sans-serif;
background: rgba(0, 0, 0, 0.5);
padding: 10px;
border-radius: 5px;
z-index: 10; /* Ensure it's above the canvas */
}
</style>
</head>
<body>
<div id="threejs-container">
<div id="overlay-text">Hello from HTML Overlay!</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
const container = document.getElementById('threejs-container');
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 5;
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>