在 Web 开发中,Three.js 是一个非常强大的 3D 渲染库,而 CSS3DRenderer
则可以将 HTML 元素(如 iframe)渲染到 3D 场景中,这使得我们可以将普通网页嵌入并展示在三维空间中,赋予 Web 页面更酷的交互形式。
本文将带你一步一步在 Vue3 项目中使用 Three.js + CSS3DRenderer
实现一个可交互、可缩放旋转的 3D 网页嵌入效果。
项目介绍
我们的目标是:
- 使用
Three.js
创建一个三维场景; - 使用
CSS3DRenderer
将网页(iframe)加载为 3D 对象; - 使用
OrbitControls
实现鼠标交互控制; - 所有内容封装在 Vue3
<script setup>
中运行。
核心技术栈
- Vue 3
- Three.js
three/examples/jsm/renderers/CSS3DRenderer.js
three/examples/jsm/controls/OrbitControls.js
代码详解
1. HTML 模板结构
<template>
<div class="box" ref="box"></div>
</template>
我们在页面中只保留一个容器 box
,所有 3D 内容都将渲染到这个容器中。
2. 初始化场景核心逻辑
下面是初始化场景的完整逻辑:
const initScene = () => new THREE.Scene();
const initCamera = (boxElement) => {
const camera = new THREE.PerspectiveCamera(75, boxElement.clientWidth / boxElement.clientHeight, 1, 6000);
camera.position.z = 3000;
camera.lookAt(0, 0, 0);
return camera;
};
const initRenderer = (boxElement) => {
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setClearColor(0xffffff); // 背景设置为白色
renderer.setSize(boxElement.clientWidth, boxElement.clientHeight);
boxElement.appendChild(renderer.domElement);
return renderer;
};
3. CSS3D 渲染器初始化
CSS3DRenderer
会创建一个可以渲染 DOM 元素的 3D 渲染器。通过它我们可以将 iframe 包装成 DOM 元素再变成 CSS3DObject
加入到场景中。
const initLabelRenderer = (boxElement) => {
const labelRenderer = new CSS3DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0';
boxElement.appendChild(labelRenderer.domElement);
return labelRenderer;
};
4. 嵌入网页模型
const initModel = (scene, w, h, url) => {
const domEle = document.createElement('div');
domEle.innerHTML = `
<div style="width:${w}px; height:${h}px;">
<iframe src="${url}" width="${w}" height="${h}" frameborder="0"></iframe>
</div>
`;
const domEleObj = new CSS3DObject(domEle);
domEleObj.position.set(0, 0, 0);
scene.add(domEleObj);
};
你可以把任意网页以 iframe 的形式加载进来,并作为 3D 模型对象在场景中展示。
5. 渲染逻辑与交互控制
创建完所有元素后,我们通过 OrbitControls
实现摄像机的缩放/旋转/平移交互:
const controls = new OrbitControls(camera, labelRenderer.domElement);
controls.update();
然后使用渲染函数刷新场景:
const render = (renderer, scene, camera, labelRenderer) => {
renderer.render(scene, camera);
labelRenderer.render(scene, camera);
};
const animate = () => {
requestAnimationFrame(animate);
render(renderer, scene, camera, labelRenderer);
};
最后在 onMounted
中初始化所有内容:
onMounted(() => {
if (!box.value) return;
const scene = initScene();
const labelRenderer = initLabelRenderer(box.value);
const camera = initCamera(box.value);
const renderer = initRenderer(box.value);
const controls = new OrbitControls(camera, labelRenderer.domElement);
controls.update();
initModel(scene, 1920, 1080, 'https://www.baidu.com/');
animate();
});
完整代码
<template>
<div class="box" ref="box"></div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; // 引入 OrbitControls
import { CSS3DRenderer, CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer.js';
const box = ref(null);
/**
* 初始化场景
*/
const initScene = () => {
const scene = new THREE.Scene();
return scene;
};
/**
* 初始化相机
* @param {*} boxElement 盒子元素
*/
const initCamera = boxElement => {
const camera = new THREE.PerspectiveCamera(
75,
boxElement.clientWidth / boxElement.clientHeight,
1,
6000
);
camera.lookAt(0, 0, 0);
camera.position.z = 3000;
// camera.position.x = 20;
// camera.position.y = 20;
return camera;
};
/**
* 初始化渲染器
* @param {*} boxElement 盒子元素
*/
const initRenderer = boxElement => {
const renderer = new THREE.WebGLRenderer({ antialias: true }); // 设置抗锯齿
renderer.setClearColor(0xffffff); // 将背景颜色设置为白色
renderer.setSize(boxElement.clientWidth, boxElement.clientHeight);
boxElement.appendChild(renderer.domElement);
return renderer;
};
/**
* 构建CSS 3D渲染器
* @param {*} boxElement 盒子元素
*/
const initLabelRenderer = boxElement => {
const labelRenderer = new CSS3DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = 0;
boxElement.appendChild(labelRenderer.domElement);
return labelRenderer;
};
/**
* 初始化模型
* @param {*} s 场景 必传
* @param {*} w 宽
* @param {*} h 高
* @param {*} url iframe链接地址
*/
const initModel = (s, w, h, url) => {
const domEle = document.createElement('div');
const html = `
<div style="width:${w}px; height:${h}px;">
<iframe src="${url}" width="${w}" height="${h}" frameborder=0></iframe>
</div>
`;
domEle.innerHTML = html;
const domEleObj = new CSS3DObject(domEle);
domEleObj.position.set(0, 0, 0);
s.add(domEleObj);
};
// 渲染场景于css3D渲染器
const render = (renderer, scene, camera, labelRenderer) => {
renderer.render(scene, camera);
labelRenderer.render(scene, camera);
};
onMounted(() => {
// 确保 box 元素存在
if (!box.value) return;
// 初始化场景
const scene = initScene();
// 初始化CSS 3D渲染器
const labelRenderer = initLabelRenderer(box.value);
// 初始化相机
const camera = initCamera(box.value);
// 初始化渲染器
const renderer = initRenderer(box.value);
// 创建 OrbitControls 控制器
const controls = new OrbitControls(camera, labelRenderer.domElement);
controls.update(); // 更新控制器以确保有效初始状态
// 初始化模型
initModel(scene, 1920, 1080, 'https://www.baidu.com/');
// 定义动画函数
const animate = () => {
requestAnimationFrame(animate); // 请求下一帧动画
render(renderer, scene, camera, labelRenderer); // 渲染场景
};
animate(); // 开始动画循环
});
</script>
<style scoped>
.box {
width: 100vw;
height: 100vh;
}
.domBox {
background-color: aqua;
}
</style>
最终效果
你可以通过鼠标拖动、缩放、旋转视角,从三维空间中观察你加载的网页内容。这种方式非常适用于以下场景:
- 可视化多个网页窗口或大屏展示;
- 将网页内容融入 3D 虚拟世界;
- 创建沉浸式交互体验。
小结
通过结合 Vue3 与 Three.js 的 CSS3D 渲染器,我们实现了一个非常炫酷的 3D 网页展示方式。你可以进一步扩展此项目,支持拖拽网页、动态加载内容、添加多个 iframe 组合成 3D 空间 UI 等。
拓展方向
- 动态切换 iframe 页面;
- 支持多 iframe 多视角布局;
- 支持鼠标拖动模型位置;
- 与 WebXR 或 WebGPU 结合实现更高级别的互动。