Skip to content

83. 实战:3D 电脑(三)

Published:

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

image.png

这节来填充网页内容。

先准备一张桌面背景:

mian.png

image.png

加一个 img 元素:

image.png

<img className="bg" src="./bg.png"/>

改下样式:

image.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 绝对定位,下面的图片相对定位。

然后再加一个应用图标:

image.png

<div className='app'>
  <div className='logo'></div>
  <div className='name'>浏览器</div>
</div>

加下样式:

image.png

#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;
}

image.png

找个浏览器图标:

24b4c549-a21e-4169-a002-8a4c1e48d3d9.png

image.png

去掉 border,加下背景:

image.png

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

image.png

然后双击的时候我们打开搜索引擎:

先加一个 iframe:

image.png

<iframe className='browser' src='/assets/threejs/b5c7c27c462211378dfee368618551a67209cb7c.image'/>

然后加一下样式:

image.png

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

2025-07-07 10.59.25.gif

这样,就能在桌面上打开网页搜索了。

然后我们点击图标的时候再打开浏览器:

image.png

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 的样式:

image.png

#desktop .app:hover {
  background:lightblue;
}

但你会发现点击事件没生效:

2025-07-07 13.24.20.gif

这是为什么呢?

看下 dom 就知道了:

image.png

用 CSS3DObject 渲染的时候,会把那个 dom 拿过来,放到单独的元素下面。

这个元素不是在 react 挂载的那个 dom 上。

而 react 绑定事件是用事件冒泡的方式,统一在根元素处理。

现在都到根元素之外了,自然就处理不到了。

所以我们换一种绑定事件的方式:

image.png

useEffect(() => {
    const ele = document.querySelector('.app');
    const handler = () => {
      setOpen(true);
    };
    ele.addEventListener('click', handler)
    return () => {
      ele.removeEventListener('click', handler);
    }
  }, []);

原生 dom 绑定事件,自然就没有 react 事件代理的问题了。

2025-07-07 13.37.28.gif

然后我们来优化一下:

image.png

给 desktop 设置隐藏:

style={{display: 'none'}}

注意,一定是 style 的 display:none,因为 CSS3DRenderer在渲染元素的时候会把它设置为 display:block

然后点击 desktop 的时候,我们把相机拉近:

把 camera 导出:

image.png

接收一下:

image.png

const cameraRef = useRef();
cameraRef.current = camera;

然后点击浏览器的时候,把镜头拉近:

image.png

const camera = cameraRef.current;
camera.position.set(500, 100, 0);

看下效果:

2025-07-07 13.58.52.gif

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

image.png

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

image.png

gsap.to(camera.position, {
    x: 500,
    y: 100,
    z: 0
    duration: 1
});

一秒运动过去。

看下效果:

2025-07-07 14.08.12.gif

2025-07-07 14.10.15.gif

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

2025-07-07 14.32.39.gif

案例代码上传了小册仓库

总结

这节我们给 3D 电脑的 CSS3DObject 加上了内容。

用 html + css 写了布局,然后点击图标弹出 iframe 的网页。

要注意 react 的事件绑定方式导致 CSS3DObject 里的内容触发不了事件,需要用原生的方式绑定。

再就是要给元素设置 display:none 的 style,这样刚开始隐藏, CSS3DRenderer 渲染它的时候设置 display:block 就会把它显示出来。

当你需要在 3D 场景里加一些用网页渲染的东西,就可以用 CSS3DRenderer 来做。

评论