Appearance
Three.js 示例
本项目包含多个 Three.js 示例,涵盖 360° 全景浏览、雨景效果等功能。
示例列表
1. 360° 全景浏览 - 球形全景
使用 Three.js 实现的球形 360° 全景浏览,支持多个房间切换、热点点击交互。
标题:
说明:
功能特性:
- 球形全景图展示
- 多个房间场景切换(客厅、厨房等)
- 热点标记(点击进入其他房间)
- 鼠标悬停显示提示框
- OrbitControls 自由视角控制
核心代码逻辑:
javascript
// 1. 创建球形几何体,贴上全景图作为纹理
const geometry = new THREE.SphereGeometry(500, 60, 40)
// 反转几何体,使贴图在内部显示
geometry.scale(-1, 1, 1)
// 2. 使用 Raycaster 检测鼠标与热点的交互
const raycaster = new THREE.Raycaster()
const mouse = new THREE.Vector2()
// 关键:使用 getBoundingClientRect 计算正确的 NDC 坐标
const rect = this.renderer.domElement.getBoundingClientRect()
mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1
mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1
raycaster.setFromCamera(mouse, this.camera)注意事项:
- Vue 3 中需用
markRaw()包裹 Three.js 对象,避免响应式代理导致报错 - 射线检测坐标必须基于画布的
getBoundingClientRect()计算
2. 360° 全景浏览 - 立方体天空盒
使用立方体 6 个面贴图实现的 360° 全景浏览。
功能特性:
- 立方体 6 面全景图
- OrbitControls 视角控制
- 自动适应窗口大小
核心代码逻辑:
javascript
// 导入 6 张方向图片
import leftImg from '@/assets/home/left.png'
import rightImg from '@/assets/home/right.png'
import topImg from '@/assets/home/top.png'
import bottomImg from '@/assets/home/bottom.png'
import frontImg from '@/assets/home/front.png'
import backImg from '@/assets/home/back.png'
// 创建立方体几何体
const boxGeometry = new THREE.BoxGeometry(10, 10, 10)
// 翻转 Z 轴使贴图在内部显示
boxGeometry.scale(10, 10, -10)
// 为每个面创建材质
const boxMaterials = picList.map((img) => {
const texture = new THREE.TextureLoader().load(img)
return new THREE.MeshBasicMaterial({ map: texture })
})
this.box = new THREE.Mesh(boxGeometry, boxMaterials)4. 雨景效果
实现下雨天气的 3D 场景效果,包含云层、雨滴、闪电等元素。
功能特性:
- 飘动的云层(使用烟雾纹理)
- 下落的雨滴粒子系统
- 随机闪电效果
- 雾效氛围
核心代码结构:
rain/
├── index.vue # 入口组件
├── Template.js # 基础模板类(场景、相机、渲染器初始化)
├── scene/
│ └── director.js # 导演类(场景编排、动画循环)
└── objects/
├── Cloud.js # 云朵类
└── RainDrop.js # 雨滴粒子类核心代码逻辑:
javascript
// 云朵动画
animate() {
this.instance.rotation.z -= 0.003
}
// 雨滴粒子动画
animate() {
const positions = this.geom.attributes.position.array
for (let i = 0; i < this.drops * 3; i += 3) {
this.velocityY[i / 3] += Math.random() * 0.05
positions[i + 1] -= this.velocityY[i / 3]
// 雨滴落地后重置到顶部
if (positions[i + 1] < -200) {
positions[i + 1] = 200
this.velocityY[i / 3] = 0.5 + Math.random() / 2
}
}
this.geom.attributes.position.needsUpdate = true
}
// 闪电随机效果
if (Math.random() > 0.93 || this.lightning.power > 100) {
this.lightning.power = 50 + Math.random() * 500
}常见问题
1. Vue 3 中 Three.js 对象报 Proxy 错误
问题: Error: Invalid typed array view: undefined 或黑屏
原因: Vue 3 的响应式系统会代理 data 中的对象,Three.js 内部使用的 typed arrays 被代理后会导致错误
解决: 使用 markRaw() 包裹 Three.js 对象
javascript
import { markRaw } from 'vue'
export default {
data() {
return {
scene: null,
camera: null,
renderer: null
}
},
methods: {
initScene() {
this.scene = markRaw(new THREE.Scene())
}
}
}2. require() 在 Vite 中不可用
问题: require is not defined
原因: Vite 使用 ES Module,不支持 CommonJS 的 require()
解决: 使用 ES6 import
javascript
// 错误
const texture = new THREE.TextureLoader().load(require('@/assets/image.png'))
// 正确
import imageUrl from '@/assets/image.png'
const texture = new THREE.TextureLoader().load(imageUrl)3. 射线检测点击位置偏移
问题: 点击热点没有反应,或点击位置与实际位置不符
原因: 鼠标坐标未转换为相对于画布的 NDC 坐标
解决: 使用 getBoundingClientRect() 计算
javascript
const rect = this.renderer.domElement.getBoundingClientRect()
mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1
mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 14. 组件销毁后内存泄漏
问题: 切换页面后 Three.js 渲染循环仍在运行
解决: 在 beforeUnmount 中清理资源
javascript
beforeUnmount() {
// 取消动画循环
if (this.timer) {
cancelAnimationFrame(this.timer)
}
// 移除事件监听
window.removeEventListener('resize', this.onResize)
// 清理渲染器
if (this.renderer) {
this.renderer.dispose()
}
}依赖安装
bash
npm install three panolens gsap路由配置
javascript
{
path: '/threeDemo/360/round',
name: 'ThreeDemoRound',
component: () => import('@/views/threeDemo/360/round.vue')
},
{
path: '/threeDemo/360/skyBox',
name: 'ThreeDemoSkyBox',
component: () => import('@/views/threeDemo/360/skyBox.vue')
},
{
path: '/threeDemo/rain',
name: 'ThreeDemoRain',
component: () => import('@/views/threeDemo/rain/index.vue')
}