CSS2DRenderer 将 HTML 元素绑定到 Three.js 对象的三维位置,不修改模型材质;需从 three/addons/ 正确导入,确保 DOM 容器与 WebGL 渲染器尺寸重叠、resize 同步更新且避免频繁 DOM 操作。

CSS2DRenderer 不是把 CSS “关联”到模型,而是把 HTML 元素(带 CSS 样式)绑定到 Three.js 对象的三维位置上——它不操作模型材质或着色器,只控制 DOM 元素的屏幕投影位置。
引入 CSS2DRenderer 和 CSS2DObject 的路径必须准确
Three.js 自 r152 起已将所有扩展移入 three/addons/ 目录,旧路径 three/examples/jsm/... 已废弃。若用 npm 安装,正确导入方式是:
import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
常见错误现象:TypeError: THREE.CSS2DRenderer is not a constructor 或 Cannot find module 'three/examples/jsm/...',基本都是路径写错或未启用 ESM 模块解析。
- 使用 Vite / Webpack 5+:确保
resolve.extensions包含.js,且未禁用默认导出解析 - CDN 引入:需额外加载
CSS2DRenderer.js,不能只靠three.min.js - Vue / React 项目中:不要在
setup()或useEffect外部直接 new 实例,避免 SSR 渲染时报错
HTML 元素必须脱离文档流并由 CSS2DRenderer 独立容器承载
CSS2DRenderer 会创建一个 <div class="css2d"> 容器,所有 <code>CSS2DObject 的 DOM 子元素都挂载其中。如果手动把 div#tag 写在页面里再传给 CSS2DObject,它会被自动移入该容器 —— 所以你写的原始位置、display: none、visibility: hidden 都无效。
立即学习“前端免费学习笔记(深入)”;
- 务必在初始化
labelRenderer = new CSS2DRenderer()后,调用labelRenderer.domElement.style.pointerEvents = 'none',否则会拦截WebGLRenderer的鼠标事件 - 容器默认
position: absolute; top: 0; left: 0; z-index: 100,需与WebGLRenderer.domElement尺寸、定位完全重叠,否则标签偏移 - 推荐做法:用 JS 动态创建标签 DOM,而非复用已有 HTML 节点,避免样式污染和生命周期冲突
标签位置依赖世界坐标,但渲染受相机视锥和 DOM 层级影响
CSS2DObject.position.set(x, y, z) 设置的是世界坐标,不是屏幕像素。它的最终显示位置由 CSS2DRenderer.render(scene, camera) 在每帧中计算得出。容易被忽略的关键点:
-
CSS2DRenderer必须在WebGLRenderer.render()之后调用,否则坐标未更新,标签“卡住”不动 - 若模型加了
group或层级嵌套,CSS2DObject应添加到同一 group 下(如mesh.add(label)),否则position是相对于 scene 原点,而非模型局部中心 - CSS 样式中禁止使用
transform: translateZ()或perspective,这些会被CSS2DRenderer忽略;仅支持top/left/scale(由渲染器注入) - 移动端 Safari 中,若
body有overflow: hidden,可能导致标签被裁剪,需检查父容器contain: layout paint是否误设
性能敏感场景下,CSS2DRenderer 的更新成本不可忽视
每个 CSS2DObject 对应一个真实 DOM 节点,频繁修改 innerHTML 或触发重排(如改 width/height)会显著拖慢帧率。尤其当标签数 > 50 时:
- 避免在
requestAnimationFrame循环里反复label.element.innerHTML = ... - 用
textContent替代innerHTML更新纯文本,减少解析开销 - 标签内容不变时,可缓存
label.element并复用,而非每帧重建CSS2DObject - 隐藏标签请用
label.visible = false,而非label.element.style.display = 'none'—— 后者仍参与布局计算
最常被跳过的一步:忘记在 window.resize 回调中同步调用 labelRenderer.setSize(width, height)。一旦窗口缩放,标签位置立刻错乱,且无任何报错提示。


















