🎯 Three.js 中的 CSS2DRenderer 与 CSS3DRenderer 全面解析
在 Three.js 项目中,很多时候我们需要把 HTML 元素 放进 3D 世界里:
比如信息标签、操作按钮、甚至一个完整的弹窗界面。
Three.js 官方提供了两种方案:
- CSS2DRenderer
- CSS3DRenderer
本文将从 区别、使用规则、示例写法、事件交互 四方面来讲解。
1️⃣ 基本区别
对比项 | CSS2DRenderer | CSS3DRenderer |
---|---|---|
渲染方式 | 元素始终面向相机,不受 3D 透视影响 | 元素作为真正的 3D 物体,可旋转、缩放、透视 |
性能 | 更轻量 | 相对更重,适合复杂 UI |
常见用途 | 标签、提示文字、简单弹窗 | 网页窗口、卡片、复杂交互界面 |
交互特点 | 像 HUD 一样叠加在场景里 | 完全沉浸式,随场景旋转变化 |
CSS 支持 | 常规 CSS,不能有 3D transform | 完整 CSS,包括 3D transform、动画 |
👉 简单一句话:
- CSS2DRenderer = 信息标签(HUD)
- CSS3DRenderer = 真实网页在 3D 世界中
2️⃣ 使用规则(怎么选)
-
规则 1:需要固定朝向相机 → 用 CSS2DRenderer
例:人物名字、坐标标签、提示文字
-
规则 2:需要 3D 效果或网页窗口 → 用 CSS3DRenderer
例:可旋转的网页卡片、嵌入式表单、3D 弹窗
-
规则 3:性能优先时 → 先考虑 CSS2DRenderer
例:大量标签(几百个),建议 CSS2DRenderer
-
规则 4:交互复杂、样式多 → 用 CSS3DRenderer
例:场景中的操作面板、功能窗口
3️⃣ 示例写法对比
✅ CSS2DRenderer 示例(标签)
js
// 创建 DOM
const labelDiv = document.createElement('div');
labelDiv.innerHTML = `
<div class="bg-black/70 text-white px-3 py-1 rounded-md text-xs">
📦 Cube 标签
</div>
`;
// 转为 CSS2DObject
const label = new THREE.CSS2DObject(labelDiv);
label.position.set(0, 2, 0); // 立方体上方
cube.add(label);
👉 适合:始终朝向相机的 标签 / 标注。
✅ CSS3DRenderer 示例(弹窗)
js
// 创建 DOM
const popupDiv = document.createElement('div');
popupDiv.innerHTML = `
<div class="bg-white shadow-lg rounded-xl w-64 p-4 text-gray-800">
<h2 class="font-bold text-lg mb-2">立方体信息</h2>
<p>这是一个 3D 弹窗</p>
<button class="mt-3 bg-blue-500 text-white px-3 py-1 rounded">
点我
</button>
</div>
`;
// 转为 CSS3DObject
const popup = new THREE.CSS3DObject(popupDiv);
popup.position.set(3, 0, 0); // 放在场景一侧
scene.add(popup);
👉 适合:带按钮、复杂 UI 的 网页弹窗。
4️⃣ 事件绑定与传参
方式一:直接绑定事件
js
popupDiv.querySelector('button').addEventListener('click', () => {
alert('按钮点击了');
});
方式二:使用自定义事件传参(推荐 🚀)
js
popupDiv.querySelector('button').addEventListener('click', () => {
const event = new CustomEvent('popupAction', { detail: { id: cube.uuid } });
window.dispatchEvent(event);
});
window.addEventListener('popupAction', e => {
console.log('传来的对象ID:', e.detail.id);
});
👉 优点:
- 结构与逻辑解耦
- 可携带参数(对象 ID、数据等)
5️⃣ 最佳实践总结
用 CSS2DRenderer:
- 标签、HUD、简单提示
- 性能优先,大量元素
html
<!DOCTYPE html>
<html>
<head>
<title>GSAP + Three.js + 弹窗 + 控制器 示例</title>
<style>
body { margin: 0; }
canvas { display: block; }
/* 弹窗样式 */
.popup {
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 12px 16px;
border-radius: 8px;
font-family: sans-serif;
text-align: center;
width: 160px;
pointer-events: auto; /* 允许弹窗接收事件 */
}
.popup button {
margin-top: 8px;
padding: 4px 8px;
border: none;
border-radius: 4px;
cursor: pointer;
pointer-events: auto; /* 确保按钮可以接收事件 */
}
</style>
<!-- Three.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
<!-- GSAP -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.4/gsap.min.js"></script>
<!-- Three.js 控制器 -->
<script src="https://cdn.jsdelivr.net/npm/three@0.134.0/examples/js/controls/OrbitControls.js"></script>
<!-- Three.js CSS2DRenderer -->
<script src="https://cdn.jsdelivr.net/npm/three@0.134.0/examples/js/renderers/CSS2DRenderer.js"></script>
</head>
<body>
<script>
// 1. Three.js 基础
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);
document.body.appendChild(renderer.domElement);
// 2. 添加控制器
const controls = new THREE.OrbitControls(camera, renderer.domElement);
// 控制器配置
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.enableZoom = true;
controls.zoomSpeed = 0.5;
controls.enablePan = true;
controls.panSpeed = 0.5;
controls.maxDistance = 50;
controls.minDistance = 2;
// 3. 立方体
const geometry = new THREE.BoxGeometry(2, 2, 2);
const material = new THREE.MeshBasicMaterial({
color: 0x00ff00,
wireframe: true
});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 10;
// 4. GSAP 动画
const tl = gsap.timeline({
repeat: -1,
yoyo: true
});
tl.to(cube.position, {
x: 5,
z: 3,
duration: 2,
ease: "power2.inOut"
}, 0)
.to(cube.rotation, {
x: Math.PI * 2,
y: Math.PI * 2,
duration: 4,
ease: "linear"
}, 0)
.to(cube.scale, {
x: 2,
y: 2,
z: 2,
duration: 1.5,
ease: "elastic.out(1, 0.3)"
});
// 5. CSS2DRenderer 用于渲染 HTML 弹窗
const labelRenderer = new THREE.CSS2DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0px';
labelRenderer.domElement.style.pointerEvents = 'none'; // 基础层不接收事件
document.body.appendChild(labelRenderer.domElement);
// 6. 创建 HTML 弹窗并绑定事件
const popupDiv = document.createElement('div');
popupDiv.className = 'popup';
popupDiv.innerHTML = `
<h3>立方体信息</h3>
<p>这是一个 3D 立方体</p>
<button id="popupBtn">点我</button>
`;
// 关键修复:使用JavaScript方式绑定事件,而不是onclick属性
const popupBtn = popupDiv.querySelector('#popupBtn');
popupBtn.addEventListener('click', () => {
// 添加一个立方体缩放效果作为反馈
gsap.to(cube.scale, {
x: cube.scale.x * 1.2,
y: cube.scale.y * 1.2,
z: cube.scale.z * 1.2,
duration: 0.3,
yoyo: true,
repeat: 1
});
alert('按钮点击成功!');
});
const popupLabel = new THREE.CSS2DObject(popupDiv);
popupLabel.position.set(0, 3, 0); // 在立方体上方
cube.add(popupLabel); // 弹窗跟随立方体移动
// 7. 窗口自适应
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.setSize(window.innerWidth, window.innerHeight);
});
// 8. 渲染循环
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
labelRenderer.render(scene, camera);
}
animate();
</script>
</body>
</html>
用 CSS3DRenderer:
- 复杂 UI(弹窗、卡片、网页模块)
- 支持 3D 旋转、缩放、动画
示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>Three.js + CSS3DRenderer + OrbitControls</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.134.0/examples/js/renderers/CSS3DRenderer.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.134.0/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body { margin: 0; overflow: hidden; }
canvas { display: block; }
/* 关键:确保CSS3D元素可以接收事件 */
#css-container {
pointer-events: auto;
}
.css-3d-object {
transform-style: preserve-3d;
pointer-events: auto;
}
</style>
</head>
<body>
<!-- 创建一个专门的容器放置CSS3D内容 -->
<div id="css-container"></div>
<script>
// 1️⃣ 场景、相机、渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 1000);
camera.position.set(0, 2, 6);
// WebGL渲染器(用于3D物体)
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// CSS3D渲染器(用于HTML元素)
const cssRenderer = new THREE.CSS3DRenderer();
cssRenderer.setSize(window.innerWidth, window.innerHeight);
cssRenderer.domElement.style.position = 'absolute';
cssRenderer.domElement.style.top = '0';
cssRenderer.domElement.style.pointerEvents = 'none'; // 让WebGL能接收事件
document.getElementById('css-container').appendChild(cssRenderer.domElement);
// 2️⃣ OrbitControls - 使用WebGL渲染器的DOM元素作为事件目标
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.minDistance = 3;
controls.maxDistance = 20;
// 3️⃣ 立方体
const cube = new THREE.Mesh(
new THREE.BoxGeometry(2, 2, 2),
new THREE.MeshNormalMaterial()
);
scene.add(cube);
// 4️⃣ CSS3D 弹窗 - 创建并配置
const createPopup = () => {
const div = document.createElement('div');
div.className = 'css-3d-object bg-white shadow-xl rounded-xl w-64 p-4 text-gray-800';
div.innerHTML = `
<h2 class="text-lg font-bold mb-2">📦 立方体信息</h2>
<p class="text-sm mb-3">可以旋转、缩放场景。</p>
<button id="scaleBtn" class="bg-blue-500 text-white px-3 py-1 rounded hover:bg-blue-600">放大立方体</button>
`;
// 绑定点击事件 - 直接在这里绑定
div.querySelector('#scaleBtn').addEventListener('click', () => {
cube.scale.multiplyScalar(1.2);
console.log('立方体已放大,当前大小:', cube.scale.x);
});
const popup = new THREE.CSS3DObject(div);
popup.position.set(3, 1, 0);
popup.scale.set(0.01, 0.01, 0.01);
return popup;
};
// 添加弹窗到场景
const popup = createPopup();
scene.add(popup);
// 5️⃣ 窗口大小调整
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
cssRenderer.setSize(window.innerWidth, window.innerHeight);
});
// 6️⃣ 渲染循环
function animate() {
requestAnimationFrame(animate);
cube.rotation.y += 0.01;
controls.update();
renderer.render(scene, camera);
cssRenderer.render(scene, camera);
}
animate();
</script>
</body>
</html>
-
推荐写法:
- 模板字符串 + TailwindCSS,开发快、样式优雅
- 事件传参用
CustomEvent
✅ 一句话结论:
轻量信息展示用 CSS2DRenderer ,复杂网页交互用 CSS3DRenderer ;写法推荐 模板字符串 + TailwindCSS ,事件传参推荐 CustomEvent。