【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 示例(标签)

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


相关推荐
折翼的恶魔3 小时前
HTML基本标签二:
前端·html
Metaphor6923 小时前
Java 将 PDF 转换为 HTML:高效解决方案与实践
java·经验分享·pdf·html
puhaha3 小时前
前端实现PDF与图片添加自定义中文水印
javascript
一树山茶3 小时前
uniapp的双token
前端·javascript
正义的大古3 小时前
OpenLayers地图交互 -- 章节六:范围交互详解
前端·javascript·vue.js·openlayers
aopstudio4 小时前
零成本上线动态博客:用 Rin + Cloudflare 部署个人博客的完整指南
javascript·serverless·github
用户6120414922134 小时前
支持eclipse+idea+mysql5和8的javaweb学生信息管理系统
java·javascript·后端
早八睡不醒午觉睡不够的程序猿5 小时前
Vue DevTools 调试提示
前端·javascript·vue.js
天天向上10245 小时前
vue el-form 自定义校验, 校验用户名调接口查重
前端·javascript·vue.js