Skip to content

49. CSS3DRenderer 实现标注:公告栏内容

Published:

上节我们学了 CSS2DRenderer,它可以给 3D 场景的物体加一个标签。

但是它始终是面向屏幕的:

2025-04-07 13.38.23.gif

并不会随着 3D 物体一起旋转、放缩。

那如果我们希望标注的信息有 3D 的感觉,会跟着旋转、放缩呢?

比如 3D 场景里有一个公告板,上面的内容是 dom 元素画的。

这种就要用 CSS3DRenderer 了。

npx create-vite css3d-annotation

image.png

进入项目,安装依赖:

npm install
npm install --save three
npm install --save-dev @types/three

改下 src/main.js

import './style.css';
import * as THREE from 'three';
import {
    OrbitControls
} from 'three/addons/controls/OrbitControls.js';
import mesh from './mesh.js';

const scene = new THREE.Scene();

scene.add(mesh);

const directionLight = new THREE.DirectionalLight(0xffffff, 2);
directionLight.position.set(500, 400, 300);
scene.add(directionLight);

const ambientLight = new THREE.AmbientLight();
scene.add(ambientLight);

const width = window.innerWidth;
const height = window.innerHeight;

const helper = new THREE.AxesHelper(500);
scene.add(helper);

const camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 10000);
camera.position.set(500, 600, 800);
camera.lookAt(0, 0, 0);

const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height)

function render() {
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}

render();

document.body.append(renderer.domElement);

const controls = new OrbitControls(camera, renderer.domElement);

创建 Scene、Light、Camera、Renderer。

改下 style.css

body {
  margin: 0;
}

然后创建 mesh.js

import * as THREE from 'three';

const planeGeometry = new THREE.PlaneGeometry(1000, 1000);
const planeMaterial = new THREE.MeshLambertMaterial({
    color: new THREE.Color('skyblue')
});

const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotateX(- Math.PI / 2);
plane.position.y = -50;

const boxGeometry = new THREE.BoxGeometry(100, 100, 100);
const boxMaterial = new THREE.MeshLambertMaterial({
    color: new THREE.Color('orange')
});
const box = new THREE.Mesh(boxGeometry, boxMaterial);

const box2 = box.clone();
box2.position.x = 200;

const mesh = new THREE.Group();
mesh.add(plane);
mesh.add(box);
mesh.add(box2);

export default mesh;

和上节一样的一个平面,两个立方体。

跑一下:

npm run dev

image.png

看下效果:

image.png

然后这次用 CSS3DObject:

image.png

image.png

import { CSS3DObject } from 'three/examples/jsm/Addons.js';
const ele = document.createElement('div');
ele.innerHTML = '<p style="background:#fff;padding: 10px;">这是 box1</p>';
const obj = new CSS3DObject(ele);
obj.position.y = 100;
box.add(obj);

const ele2 = document.createElement('div');
ele2.innerHTML = '<p style="background:#fff;padding: 10px;">这是 box2</p>';
const obj2 = new CSS3DObject(ele2);
obj2.position.y = 100;
box2.add(obj2);

分别在 box 和 box2 下面添加一个 CSS3DObject,它的参数是一个 dom 元素。

在 main.js 里用 CSS3DRenderer:

image.png

import { CSS3DRenderer } from 'three/examples/jsm/Addons.js';
const css3Renderer = new CSS3DRenderer();
css3Renderer.setSize(width, height);

const div = document.createElement('div');
div.style.position = 'relative';
div.appendChild(css3Renderer.domElement);
css3Renderer.domElement.style.position = 'absolute';
css3Renderer.domElement.style.left = '0px';
css3Renderer.domElement.style.top = '0px';
css3Renderer.domElement.style.pointerEvents = 'none';

div.appendChild(renderer.domElement);
document.body.appendChild(div);

function render() {
    css3Renderer.render(scene, camera);
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}

render();

// document.body.append(renderer.domElement);

和 CSS2DRenderer 一样,它会返回一个 domElement

我们创建一个 div,把两个 domElement 放进去,并且让 css2dRenderer.domElement 绝对定位并且不响应鼠标事件。

在 render 循环里调用 css3dRenderer.render 来一帧帧渲染。

看下效果:

2025-04-07 23.44.50.gif

有很浓的 3D 感,对比下之前 CSS2D 的:

2025-04-07 13.38.23.gif

那 CSS3DRenderer 也是基于 dom 实现的么?

image.png

打开 devtools 可以看到,它同样是 html、css,只不过用的是 css3 的 3d 相关样式。

这样看起来就和 3d 场景结合的很好了。

2025-04-07 23.48.05.gif

不过现在转到反面文字就反了,看起来怪怪的。

我们设置下 backface-visibility 样式:

image.png

ele2.style.backfaceVisibility = 'hidden';

这样转到反面就看不到了:

2025-04-07 23.53.09.gif

我们可以用 CSS3DObject 来实现公告栏的效果。

创建 mesh2.js

import * as THREE from 'three';

const geometry = new THREE.BoxGeometry(800, 500, 100);
const material = new THREE.MeshLambertMaterial({
    color: new THREE.Color('skyblue')
});

const mesh = new THREE.Mesh(geometry, material);

export default mesh;

用一个立方体来做公告栏。

image.png

image.png

然后用 CSS3DObject 把网页放上去作为公告内容:

image.png

const ele = document.createElement('div');
ele.innerHTML = `<div style="background:#fff;width:700px;height:400px;">
    <h1>这是网页</h1>
    <div style="display:flex;">
        <img src="/assets/threejs/f2639797e4bbc04d2cb256aed99963149f77d96f.jpg" style="max-height:300px"/>
        <div>
            <p>这是一条新闻</p>
            <p>这是一条新闻</p>
            <p>这是一条新闻</p>
            <p>这是一条新闻</p>
        </div>
    </div>
</div>`;
ele.style.backfaceVisibility = 'hidden';

const obj = new CSS3DObject(ele);
obj.position.y = 0;
mesh.add(obj);

其实实际上都是用前端框架渲染完之后,再 dom.getElementById 拿过来用,这里我们就直接写 html 了。

看下效果:

2025-04-08 00.09.03.gif

相比 CSS2D 只是用来做标注,他可以用来做一些电视内容、公告栏内容等,把网页 dom 元素放进去。

案例代码上传了小册仓库

总结

这节我们学了 CSS3DRenderer,它也是用 dom 给 3D 场景的物体加标注,但它是用 css3 的 3d 样式来写的,可以随场景一起旋转、放缩,有 3D 的感觉。

除了用来标注外,也可以做电视内容、公告栏内容等,在这些位置渲染网页。

CSS3DRenderer 和 CSS2DRenderer 都是非常常用的给 3D 场景添加标注的方式。

评论