【ThreeJs】【HTML载入】Three.js 中的 CSS2DRenderer 与 CSS3DRenderer 全面解析
🎯 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 示例(标签)
// 创建 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 示例(弹窗)
// 创建 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️⃣ 事件绑定与传参
方式一:直接绑定事件
popupDiv.querySelector('button').addEventListener('click', () => {alert('按钮点击了');
});
方式二:使用自定义事件传参(推荐 🚀)
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、简单提示
- 性能优先,大量元素
<!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 旋转、缩放、动画
示例
<!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。