首页
/ three.js CSS3DObject 详解:用 CSS3DRenderer 将 DOM 元素纳入 3D 场景图

three.js CSS3DObject 详解:用 CSS3DRenderer 将 DOM 元素纳入 3D 场景图

2026-09-06 13:09:35作者:咎竹峻Karen

CSS3DObject 是 three.js 中用于把任意 DOM 元素包装成 3D 场景节点的基础对象,它由 CSS3DRenderer 渲染,通过 CSS3 transform 属性实现对 DOM 元素的层级化 3D 变换。读完本文,你将掌握 CSS3DObject 的完整 API(构造函数、属性与底层行为),理解渲染器如何把 matrixWorld 转译为 CSS 矩阵,并能参考仓库官方示例(元素周期表、WebGL 混合渲染)独立完成“DOM 即 3D 对象”的实战开发。

three.js CSS3D 示例运行效果:css3d_mixed 中 WebGL 场景与 iframe 内 CSS3DObject 的混合渲染,以及 css3d_periodictable 中用 CSS3DObject 排布的元素周期表

一、CSS3DObject 的定位与继承关系

官方文档页面 CSS3DObject 给出其继承链:

Inheritance: EventDispatcher → Object3D → CSS3DObject

也就是说,CSS3DObject 直接继承自 three.js 核心类 Object3D,具备完整的场景图能力:positionrotationscaleadd()traverse()layersonBeforeRender 等 API 全部可用。它本身不携带任何几何体或材质,其“外观”完全由传入的 DOM 元素决定。

与之配套的是同文件导出的 CSS3DRendererCSS3DSprite,三者共同定义在 examples/jsm/renderers/CSS3DRenderer.js

  • CSS3DRenderer:负责把场景中所有 CSS3DObject / CSS3DSprite 的位置、旋转、缩放转译为 DOM 元素的 CSS transform
  • CSS3DSpriteCSS3DObject 的 billboard(广告牌)变体,元素始终面向相机;
  • CSS3DObject:本文主角,承载“DOM 元素 + 场景图变换”这一基础语义。

CSS3DRenderer 的文档注释明确说明了适用场景:既可用于在不使用 canvas 渲染的情况下给网站施加 3D 效果,也可用于将 DOM 元素与 WebGL 内容组合(见 CSS3DRenderer 文档页)。

二、引入方式:作为 Addon 显式导入

CSS3DObject 属于 addon(扩展模块),不在 three 核心包中,必须显式从 addons 路径导入:

import { CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';

仓库 package.jsonexports 字段保证了该导入路径的有效性:"./addons/*": "./examples/jsm/*",因此 three/addons/renderers/CSS3DRenderer.js 实际解析到仓库中的 examples/jsm/renderers/CSS3DRenderer.js

在使用原生 import map 的 HTML 页面(不经过打包器)中,可参照官方示例的写法(见 examples/css3d_periodictable.html):

<script type="importmap">
	{
		"imports": {
			"three": "../build/three.module.js",
			"three/addons/": "./jsm/"
		}
	}
</script>

三、构造函数:new CSS3DObject( element )

3.1 参数说明

参数 类型 默认值 说明
element HTMLElement document.createElement('div') 定义该 3D 对象外观的 DOM 元素,可以是 diviframeimg 等任意 HTML 元素

源码位于 CSS3DRenderer.js 第 20-84 行,构造函数签名为 constructor( element = document.createElement( 'div' ) )

3.2 构造函数内部自动完成的四件事

阅读构造函数实现(第 27-72 行),创建 CSS3DObject 时框架会自动:

  1. 统一内联样式element.style.position = 'absolute'pointerEvents = 'auto'userSelect = 'none',并设置 draggable 属性为 false。这让 DOM 元素脱离正常文档流,交给渲染器的 transform 控制,同时默认保留自身交互能力(pointerEvents: auto 表示元素上的链接、按钮可点击);
  2. 类型标记this.isCSS3DObject = true
  3. 保存元素引用this.element = element(只读);
  4. 注册 removed 监听:当对象从场景图中被移除时,traverse 其所有子对象,把仍挂载在文档中的 element 从 DOM 中 remove()。这意味着从场景中删除 CSS3DObject 会同步清理 DOM,避免残留“幽灵”元素。
constructor( element = document.createElement( 'div' ) ) {

	super();

	this.isCSS3DObject = true;
	this.element = element;
	this.element.style.position = 'absolute';
	this.element.style.pointerEvents = 'auto';
	this.element.style.userSelect = 'none';
	this.element.setAttribute( 'draggable', false );

	this.addEventListener( 'removed', function () {
		this.traverse( function ( object ) {
			if (
				object.element &&
				object.element instanceof object.element.ownerDocument.defaultView.Element &&
				object.element.parentNode !== null
			) {
				object.element.remove();
			}
		} );
	} );

}

3.3 copy():元素会随对象一起深拷贝

CSS3DObject 覆写了 Object3D.copy(),在复制变换等场景图属性的同时,会用 source.element.cloneNode( true ) 把 DOM 元素连同其子树完整克隆到新对象上(第 74-82 行):

copy( source, recursive ) {

	super.copy( source, recursive );
	this.element = source.element.cloneNode( true );
	return this;

}

因此克隆一个 CSS3DObject 不会与原对象共享同一个 DOM 节点,这是把它当作可复制的场景图节点使用时的关键保障。

四、属性一览

4.1 .element : HTMLElement(只读)

定义该 3D 对象外观的 DOM 元素。渲染器每帧读取的场景对象只有 elementmatrixWorld,所以对 element 的常规 DOM 操作(修改内容、绑定事件、切换 class)都不会影响 3D 变换。

4.2 .isCSS3DObject : boolean(只读)

类型测试标记,默认值为 true。典型用途是在遍历场景图时区分节点类型:

scene.traverse( ( object ) => {
	if ( object.isCSS3DObject ) {
		// 该节点是一个 DOM 包装对象
	}
} );

渲染器内部正是依靠这个标记(以及 isCSS3DSprite)决定对子树的处理分支(renderObject,第 362-440 行)。

五、渲染管线:matrixWorld 如何变成 CSS transform

CSS3DObject 自身不含渲染逻辑,真正“画”出它的是 CSS3DRenderer.render( scene, camera )。理解这条调用链,是正确摆放 CSS3DObject 的前提:

  1. 相机层render 先用 camera.projectionMatrix.elements[ 5 ] * _heightHalf 计算视口焦距 fov,再把 camera.matrixWorldInverse 拼成 matrix3d(...),配合 perspective(fov px)(透视相机)或正交相机的 scale + translate 组合,写入 cameraElement.style.transform。相机样式带缓存,只有变换变化时才写 DOM;
  2. 对象层renderObject 递归遍历场景图。对每个可见且通过 object.layers.test( camera.layers )CSS3DObject,取其 object.matrixWorld 生成 getObjectCSSMatrix —— 即 translate(-50%,-50%) matrix3d(...16 个分量...)。开头的 translate(-50%,-50%) 使元素以自身中心为原点参与 3D 变换,与 Object3D 的“位置即中心”语义一致;
  3. 缓存与挂载:对象的样式字符串存入 WeakMapcache.objects),只有 transform 真正变化时才更新;元素始终被挂到 cameraElement 容器下,visible === false 的对象则整棵子树设为 display: none
  4. 渲染回调:写入 transform 前后会分别调用 object.onBeforeRender( renderer, scene, camera )object.onAfterRender(...),与 three.js 核心渲染器的事件约定保持一致,便于在其中做逐帧调整。

从源码结构看,正交相机与透视相机走不同的 CSS 前缀:正交相机使用 scale(fov) translate(tx, ty) matrix3d(...),透视相机使用 perspective(fov) translateZ(fov) matrix3d(...)。因此 CSS3DObject 同时兼容 PerspectiveCameraOrthographicCamera(官方提供了 examples/css3d_orthographic.html 正交相机示例验证)。

渲染器的关键 API 速查

API 说明
new CSS3DRenderer( parameters ) parameters.element 可选,指定渲染器追加子元素的容器;不传则新建 div
.domElement 渲染器容器元素,需手动 appendChild 到页面并设置 setSize
.render( scene, camera ) 每帧调用,遍历场景更新所有 CSS3DObject 的 transform
.setSize( width, height ) 设置容器与视口尺寸,窗口 resize 时必须同步调用
.getSize() 返回 { width, height }

六、实战示例一:CSS3DObject 驱动的元素周期表

仓库示例 examples/css3d_periodictable.htmlCSS3DObject 最典型的用法:用纯 DOM 卡片构建元素周期表,在 TABLE / SPHERE / HELIX / GRID 四种空间布局间做补间动画,完全不用 WebGL。

核心步骤拆解(对应示例中 init() 与渲染循环):

  1. 构建 DOM 卡片:每个元素是一个 div.element,内含原子序数、符号、名称与相对原子质量子节点;
  2. 包装并加入场景
const element = document.createElement( 'div' );
element.className = 'element';
// ... 追加 number / symbol / details 子节点

const objectCSS = new CSS3DObject( element );
objectCSS.position.x = Math.random() * 4000 - 2000;
objectCSS.position.y = Math.random() * 4000 - 2000;
objectCSS.position.z = Math.random() * 4000 - 2000;
scene.add( objectCSS );

注意这里只设置 positionCSS3DObject 继承自 Object3D,旋转、缩放、嵌套进 Group 等一切场景图操作都可用;

  1. 创建渲染器并挂载
renderer = new CSS3DRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.getElementById( 'container' ).appendChild( renderer.domElement );
  1. 交互TrackballControls 直接绑定在 renderer.domElement 上,因为 CSS3D 渲染走 DOM 而非 canvas,控制器必须使用 CSS3D 渲染器的容器才能接收事件:
controls = new TrackballControls( camera, renderer.domElement );
controls.minDistance = 500;
controls.maxDistance = 6000;
controls.addEventListener( 'change', render );
  1. 每帧渲染
function render() {
	renderer.render( scene, camera );
}

七、实战示例二:WebGL 与 CSS3DObject 混合渲染

examples/css3d_mixed.html 展示了文档注释中提到的另一大应用场景——DOM 与 WebGL 内容组合。它把 CSS3DObject 包装的 iframe 放进与 WebGL 场景共用同一 SceneCamera 的“相框”中,两者逐帧同步变换:

rendererCSS3D = new CSS3DRenderer();
rendererCSS3D.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( rendererCSS3D.domElement );

rendererWebGL = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
rendererWebGL.domElement.style.position = 'absolute';
rendererWebGL.domElement.style.top = '0';
rendererWebGL.domElement.style.pointerEvents = 'none'; // 让事件穿透给下层
// ...
const scene = new THREE.Scene();
// scene 中同时放入 WebGL 的 Mesh / LineSegments 与 CSS3DObject

// 把 iframe 包成 3D 对象,加进同一个 scene
const iframe = document.createElement( 'iframe' );
iframe.style.width = '1028px';
iframe.style.height = '768px';
iframe.style.border = '0px';
iframe.style.backfaceVisibility = 'hidden';
iframe.src = './#webgl_animation_keyframes';
scene.add( new CSS3DObject( iframe ) );

function animate() {
	controls.update();
	rendererWebGL.render( scene, camera );
	rendererCSS3D.render( scene, camera ); // 同一个 scene、同一个 camera
}

该示例中有三个值得注意的混合渲染技巧:

  • 事件穿透与切换:WebGL canvas 设置 pointerEvents = 'none',交互交给独立的全屏 controlsDomElement;同时在 OrbitControlsstart / end 事件里临时把 iframe.style.pointerEvents 切换为 none / auto,保证拖动相机时 iframe 不“截获”鼠标;
  • backfaceVisibility: 'hidden':避免 iframe 翻转 180 度后显示为镜像;
  • alpha 混合:WebGL 渲染器开启 alpha: true,让 CSS3D 层与 WebGL 层在页面中正确叠放。

仓库中还有更多同类示例可供参考:examples/css3d_molecules.html(分子结构)、examples/css3d_youtube.html(YouTube 视频作为 3D 面)、examples/css3d_sandbox.html(交互沙盒)、examples/css3d_orthographic.html(正交相机)。

八、进阶:CSS3DSprite 与使用限制

8.1 同类对象 CSS3DSprite

CSS3DSprite 继承自 CSS3DObject第 93-133 行),把 DOM 元素渲染为始终面向相机的 sprite:渲染器在 renderObject 中检测到 object.isCSS3DSprite 后,用相机姿态矩阵替换对象自身的旋转(保留位置与缩放),并额外支持 rotation2D 属性(弧度)在屏幕平面内旋转元素。copy() 也会同步复制 rotation2D。示例见 examples/css3d_sprites.html

8.2 使用限制(务必知悉)

CSS3DRenderer 的 JSDoc 注释(第 140-156 行)列出了三条硬限制:

  1. 不能使用 three.js 的材质系统——外观完全由 DOM/CSS 决定,MeshStandardMaterial 等与之无关;
  2. 不能使用几何体——没有 BufferGeometry、光照、阴影参与;
  3. 仅支持 100% 的浏览器/显示器缩放——CSS 像素与 three.js 世界单位之间的换算依赖 1:1 的屏幕缩放,浏览器缩放比例非 100% 时会出现透视错位。

另外,由于 CSS3DObject 的交互能力来自真实 DOM,它也天然具备 WebGL canvas 难以做到的优势:文本可选中排版(除非 userSelect 被改写)、链接可跳转、可内嵌 iframe 运行任意 Web 应用。

九、小结

  • CSS3DObject 继承 Object3D,以 element(DOM 元素)+ 场景图变换为核心,由 CSS3DRenderer 通过 CSS matrix3d 转译渲染;
  • 构造时自动接管元素定位样式并注册 DOM 清理逻辑,copy() 通过 cloneNode(true) 深拷贝元素;
  • CSS3DSprite 组合可覆盖“平面 3D 对象”与“广告牌”两类需求;
  • 典型场景:无 canvas 的 DOM 3D 站点(元素周期表)、WebGL 与 DOM 混合渲染(iframe 相框)、可交互 3D 界面;
  • 全部源码集中于 examples/jsm/renderers/CSS3DRenderer.js,文档页为 docs/pages/CSS3DObject.html,配合 examples/css3d_*.html 系列示例即可完整复现本文所有用法。
登录后查看全文
热门项目推荐
相关项目推荐