前面搞定了 3D 场景的搭建和 CSS3DObject 的位置确定:

这节来填充网页内容。
先准备一张桌面背景:


加一个 img 元素:

<img className="bg" src="./bg.png"/>
改下样式:

#desktop {
width: 600px;
height: 1100px;
background: pink;
backface-visibility: hidden;
position: relative;
}
#desktop .bg {
position: absolute;
left: -250px;
top: 250px;
width: 1100px;
height: 600px;
rotate: -90deg;
}
desktop 绝对定位,下面的图片相对定位。
然后再加一个应用图标:

<div className='app'>
<div className='logo'></div>
<div className='name'>浏览器</div>
</div>
加下样式:

#desktop .app {
position: absolute;
left: 50px;
top: 900px;
width: 100px;
height: 100px;
rotate: -90deg;
border: 1px solid blue;
}
#desktop .app .logo{
width: 50px;
height: 50px;
background: blue;
translate: 50% 0;
}
#desktop .app .name{
font-size: 30px;
color: white;
}

找个浏览器图标:


去掉 border,加下背景:

background: url(./chrome.png);
background-size: cover;

然后双击的时候我们打开搜索引擎:
先加一个 iframe:

<iframe className='browser' src='/assets/threejs/b5c7c27c462211378dfee368618551a67209cb7c.image'/>
然后加一下样式:

#desktop .browser {
width: 900px;
height: 600px;
position: absolute;
left: -150px;
top: 250px;
rotate: -90deg;
}

这样,就能在桌面上打开网页搜索了。
然后我们点击图标的时候再打开浏览器:

const [open, setOpen] = useState(false);
return <div>
<div id="main">
<div id="content">
</div>
<div id="desktop">
<img className="bg" src="./bg.png"/>
<div className='app' onDoubleClick={() => setOpen(true)}>
<div className='logo'></div>
<div className='name'>浏览器</div>
</div>
<iframe className='browser' style={{display: open ? 'block' : 'none'}} src='/assets/threejs/b5c7c27c462211378dfee368618551a67209cb7c.image'/>
</div>
</div>
</div>
加一个状态来控制浏览器的显示隐藏,双击的时候改变状态。
顺便加个 hover 的样式:

#desktop .app:hover {
background:lightblue;
}
但你会发现点击事件没生效:

这是为什么呢?
看下 dom 就知道了:

用 CSS3DObject 渲染的时候,会把那个 dom 拿过来,放到单独的元素下面。
这个元素不是在 react 挂载的那个 dom 上。
而 react 绑定事件是用事件冒泡的方式,统一在根元素处理。
现在都到根元素之外了,自然就处理不到了。
所以我们换一种绑定事件的方式:

useEffect(() => {
const ele = document.querySelector('.app');
const handler = () => {
setOpen(true);
};
ele.addEventListener('click', handler)
return () => {
ele.removeEventListener('click', handler);
}
}, []);
原生 dom 绑定事件,自然就没有 react 事件代理的问题了。

然后我们来优化一下:

给 desktop 设置隐藏:
style={{display: 'none'}}
注意,一定是 style 的 display:none,因为 CSS3DRenderer在渲染元素的时候会把它设置为 display:block
然后点击 desktop 的时候,我们把相机拉近:
把 camera 导出:

接收一下:

const cameraRef = useRef();
cameraRef.current = camera;
然后点击浏览器的时候,把镜头拉近:

const camera = cameraRef.current;
camera.position.set(500, 100, 0);
看下效果:

然后点击其他地方的时候,回到原位置:

const handler2 =() => {
setOpen(false);
camera.position.set(1200, 500, 0);
}
document.addEventListener('click', handler2);
return () => {
ele.removeEventListener('click', handler);
document.removeEventListener('click', handler2);
}
然后我们用 gsap 加个缓动动画:
pnpm install --save gsap

gsap.to(camera.position, {
x: 500,
y: 100,
z: 0
duration: 1
});
一秒运动过去。
看下效果:


去掉坐标轴,我们整体看下:

案例代码上传了小册仓库
总结
这节我们给 3D 电脑的 CSS3DObject 加上了内容。
用 html + css 写了布局,然后点击图标弹出 iframe 的网页。
要注意 react 的事件绑定方式导致 CSS3DObject 里的内容触发不了事件,需要用原生的方式绑定。
再就是要给元素设置 display:none 的 style,这样刚开始隐藏, CSS3DRenderer 渲染它的时候设置 display:block 就会把它显示出来。
当你需要在 3D 场景里加一些用网页渲染的东西,就可以用 CSS3DRenderer 来做。