1. 为什么选择VueThree.js做VR看房这两年VR看房突然火了起来很多房产平台都在用。作为前端开发者我发现用VueThree.js这套组合拳来实现VR看房特别顺手。先说Vue它那个响应式数据绑定简直是为交互场景量身定做的Three.js又是WebGL的顶级封装两者配合起来就像咖啡配奶泡——完美。我去年做过一个中介平台的VR看房项目客户要求能在网页里自由走动看房。当时试过纯Three.js方案发现状态管理特别麻烦。后来改用VueThree.js把场景状态都存在Vuex里开发效率直接翻倍。比如用户点击下一间房按钮Vue这边改个数据Three.js场景就自动更新了这种开发体验不要太爽。2. 环境搭建与基础配置2.1 创建Vue项目先来个标准的Vue项目初始化npm init vuelatest vr-house-viewer cd vr-house-viewer npm install重点来了Three.js的安装有讲究。我建议用官方包而不是某些第三方封装npm install three types/three踩坑提醒记得在vite.config.ts里加个配置否则Three.js的示例模型可能加载失败export default defineConfig({ optimizeDeps: { exclude: [three] } })2.2 初始化Three.js场景在components文件夹新建VRViewer.vue先写个基础架子script setup import * as THREE from three import { onMounted, ref } from vue const canvasRef ref(null) onMounted(() { // 初始化场景 const scene new THREE.Scene() const camera new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ) const renderer new THREE.WebGLRenderer({ canvas: canvasRef.value, antialias: true }) // 后续代码... }) /script template canvas refcanvasRef / /template3. 全景图处理与球体创建3.1 获取全景图素材找全景图是个技术活。我常用的免费资源站有Poly HavenCC0协议可商用Texture Haven建筑类素材多各大相机厂商的样张库专业项目建议用Insta360这类设备拍摄手机拍的全景图往往顶部/底部有畸变。有个取巧办法用Matterport扫描的房屋导出等距柱状投影图。3.2 创建球体模型重点代码来了这里有几个关键参数要注意const geometry new THREE.SphereGeometry( 500, // 半径 60, // 宽度分段数 40, // 高度分段数 Math.PI, // 水平起始角度 Math.PI * 2, // 水平扫描角度 Math.PI / 2, // 垂直起始角度 Math.PI // 垂直扫描角度 )为什么半径要设500实测发现值太小用户稍微移动就会穿模值太大浮点数精度问题导致闪烁500-1000这个范围最合适4. 材质应用与相机设置4.1 加载全景贴图用TextureLoader加载图片时一定要处理加载状态const textureLoader new THREE.TextureLoader() const texture textureLoader.load( /panorama.jpg, (texture) { texture.mapping THREE.EquirectangularReflectionMapping texture.colorSpace THREE.SRGBColorSpace }, undefined, (err) { console.error(图片加载失败:, err) } )4.2 相机定位技巧相机位置设置有个小窍门camera.position.set(0, 1.6, 0) // 1.6米是成年人平均视高 camera.lookAt(0, 1.6, -1) // 看向正前方建议加上轨道控制器方便调试import { OrbitControls } from three/addons/controls/OrbitControls.js const controls new OrbitControls(camera, renderer.domElement) controls.enableZoom true controls.target.set(0, 1.6, 0)5. 交互功能实现5.1 热点标记在墙上添加可点击的热点const hotspotGeometry new THREE.SphereGeometry(0.2, 16, 16) const hotspotMaterial new THREE.MeshBasicMaterial({ color: 0xff0000 }) const hotspot new THREE.Mesh(hotspotGeometry, hotspotMaterial) hotspot.position.set(3, 1.5, -4) // 放在墙面位置 // 添加点击事件 window.addEventListener(click, (event) { const mouse new THREE.Vector2( (event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 1 ) const raycaster new THREE.Raycaster() raycaster.setFromCamera(mouse, camera) const intersects raycaster.intersectObjects([hotspot]) if (intersects.length 0) { console.log(点击了热点) } })5.2 多房间切换用Vue的响应式特性管理房间状态const rooms ref([ { id: 1, panorama: /room1.jpg, hotspots: [...] }, { id: 2, panorama: /room2.jpg, hotspots: [...] } ]) const currentRoom ref(0) function switchRoom(index) { currentRoom.value index loadPanorama(rooms.value[index].panorama) }6. 性能优化方案6.1 图片压缩技巧全景图体积大建议使用JPEG格式质量设70-80%分辨率控制在8000x4000以内启用渐进式加载可以用sharp库预处理图片npm install sharp然后写个转换脚本const sharp require(sharp) sharp(input.jpg) .resize(8000, 4000) .jpeg({ quality: 80, progressive: true }) .toFile(output.jpg)6.2 内存管理Three.js容易内存泄漏记得在组件卸载时清理onBeforeUnmount(() { renderer.dispose() geometry.dispose() material.dispose() texture.dispose() })7. 移动端适配要点7.1 陀螺仪控制加上设备方向检测if (window.DeviceOrientationEvent) { window.addEventListener(deviceorientation, (event) { const alpha event.alpha ? THREE.MathUtils.degToRad(event.alpha) : 0 const beta event.beta ? THREE.MathUtils.degToRad(event.beta) : 0 const gamma event.gamma ? THREE.MathUtils.degToRad(event.gamma) : 0 camera.rotation.set(beta, alpha, -gamma) }) }7.2 触摸事件处理实现双指缩放let initialDistance 0 canvasRef.value.addEventListener(touchstart, (e) { if (e.touches.length 2) { initialDistance Math.hypot( e.touches[0].pageX - e.touches[1].pageX, e.touches[0].pageY - e.touches[1].pageY ) } }) canvasRef.value.addEventListener(touchmove, (e) { if (e.touches.length 2) { const distance Math.hypot( e.touches[0].pageX - e.touches[1].pageX, e.touches[0].pageY - e.touches[1].pageY ) const zoom distance / initialDistance camera.fov initialFov / zoom camera.updateProjectionMatrix() } })8. 常见问题排查8.1 图片显示黑色可能原因及解决方案图片路径错误 - 用require()包裹路径跨域问题 - 确保图片服务器配置CORS纹理未翻转 - 记得设置geometry.scale(1,1,-1)8.2 性能卡顿优化策略降低球体分段数但不要低于32使用低分辨率贴图预览高清图延迟加载启用renderer.outputColorSpace THREE.SRGBColorSpace最后提醒下正式项目建议用PhotoSphereViewer这类成熟库但理解底层原理很重要。我有个项目开始时直接用现成库后来要加特殊效果时不得不重写早知道不如一开始就用Three.js自己实现。
Vue + Three.js:从全景图到沉浸式VR看房实战指南
发布时间:2026/5/15 12:17:15
1. 为什么选择VueThree.js做VR看房这两年VR看房突然火了起来很多房产平台都在用。作为前端开发者我发现用VueThree.js这套组合拳来实现VR看房特别顺手。先说Vue它那个响应式数据绑定简直是为交互场景量身定做的Three.js又是WebGL的顶级封装两者配合起来就像咖啡配奶泡——完美。我去年做过一个中介平台的VR看房项目客户要求能在网页里自由走动看房。当时试过纯Three.js方案发现状态管理特别麻烦。后来改用VueThree.js把场景状态都存在Vuex里开发效率直接翻倍。比如用户点击下一间房按钮Vue这边改个数据Three.js场景就自动更新了这种开发体验不要太爽。2. 环境搭建与基础配置2.1 创建Vue项目先来个标准的Vue项目初始化npm init vuelatest vr-house-viewer cd vr-house-viewer npm install重点来了Three.js的安装有讲究。我建议用官方包而不是某些第三方封装npm install three types/three踩坑提醒记得在vite.config.ts里加个配置否则Three.js的示例模型可能加载失败export default defineConfig({ optimizeDeps: { exclude: [three] } })2.2 初始化Three.js场景在components文件夹新建VRViewer.vue先写个基础架子script setup import * as THREE from three import { onMounted, ref } from vue const canvasRef ref(null) onMounted(() { // 初始化场景 const scene new THREE.Scene() const camera new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ) const renderer new THREE.WebGLRenderer({ canvas: canvasRef.value, antialias: true }) // 后续代码... }) /script template canvas refcanvasRef / /template3. 全景图处理与球体创建3.1 获取全景图素材找全景图是个技术活。我常用的免费资源站有Poly HavenCC0协议可商用Texture Haven建筑类素材多各大相机厂商的样张库专业项目建议用Insta360这类设备拍摄手机拍的全景图往往顶部/底部有畸变。有个取巧办法用Matterport扫描的房屋导出等距柱状投影图。3.2 创建球体模型重点代码来了这里有几个关键参数要注意const geometry new THREE.SphereGeometry( 500, // 半径 60, // 宽度分段数 40, // 高度分段数 Math.PI, // 水平起始角度 Math.PI * 2, // 水平扫描角度 Math.PI / 2, // 垂直起始角度 Math.PI // 垂直扫描角度 )为什么半径要设500实测发现值太小用户稍微移动就会穿模值太大浮点数精度问题导致闪烁500-1000这个范围最合适4. 材质应用与相机设置4.1 加载全景贴图用TextureLoader加载图片时一定要处理加载状态const textureLoader new THREE.TextureLoader() const texture textureLoader.load( /panorama.jpg, (texture) { texture.mapping THREE.EquirectangularReflectionMapping texture.colorSpace THREE.SRGBColorSpace }, undefined, (err) { console.error(图片加载失败:, err) } )4.2 相机定位技巧相机位置设置有个小窍门camera.position.set(0, 1.6, 0) // 1.6米是成年人平均视高 camera.lookAt(0, 1.6, -1) // 看向正前方建议加上轨道控制器方便调试import { OrbitControls } from three/addons/controls/OrbitControls.js const controls new OrbitControls(camera, renderer.domElement) controls.enableZoom true controls.target.set(0, 1.6, 0)5. 交互功能实现5.1 热点标记在墙上添加可点击的热点const hotspotGeometry new THREE.SphereGeometry(0.2, 16, 16) const hotspotMaterial new THREE.MeshBasicMaterial({ color: 0xff0000 }) const hotspot new THREE.Mesh(hotspotGeometry, hotspotMaterial) hotspot.position.set(3, 1.5, -4) // 放在墙面位置 // 添加点击事件 window.addEventListener(click, (event) { const mouse new THREE.Vector2( (event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 1 ) const raycaster new THREE.Raycaster() raycaster.setFromCamera(mouse, camera) const intersects raycaster.intersectObjects([hotspot]) if (intersects.length 0) { console.log(点击了热点) } })5.2 多房间切换用Vue的响应式特性管理房间状态const rooms ref([ { id: 1, panorama: /room1.jpg, hotspots: [...] }, { id: 2, panorama: /room2.jpg, hotspots: [...] } ]) const currentRoom ref(0) function switchRoom(index) { currentRoom.value index loadPanorama(rooms.value[index].panorama) }6. 性能优化方案6.1 图片压缩技巧全景图体积大建议使用JPEG格式质量设70-80%分辨率控制在8000x4000以内启用渐进式加载可以用sharp库预处理图片npm install sharp然后写个转换脚本const sharp require(sharp) sharp(input.jpg) .resize(8000, 4000) .jpeg({ quality: 80, progressive: true }) .toFile(output.jpg)6.2 内存管理Three.js容易内存泄漏记得在组件卸载时清理onBeforeUnmount(() { renderer.dispose() geometry.dispose() material.dispose() texture.dispose() })7. 移动端适配要点7.1 陀螺仪控制加上设备方向检测if (window.DeviceOrientationEvent) { window.addEventListener(deviceorientation, (event) { const alpha event.alpha ? THREE.MathUtils.degToRad(event.alpha) : 0 const beta event.beta ? THREE.MathUtils.degToRad(event.beta) : 0 const gamma event.gamma ? THREE.MathUtils.degToRad(event.gamma) : 0 camera.rotation.set(beta, alpha, -gamma) }) }7.2 触摸事件处理实现双指缩放let initialDistance 0 canvasRef.value.addEventListener(touchstart, (e) { if (e.touches.length 2) { initialDistance Math.hypot( e.touches[0].pageX - e.touches[1].pageX, e.touches[0].pageY - e.touches[1].pageY ) } }) canvasRef.value.addEventListener(touchmove, (e) { if (e.touches.length 2) { const distance Math.hypot( e.touches[0].pageX - e.touches[1].pageX, e.touches[0].pageY - e.touches[1].pageY ) const zoom distance / initialDistance camera.fov initialFov / zoom camera.updateProjectionMatrix() } })8. 常见问题排查8.1 图片显示黑色可能原因及解决方案图片路径错误 - 用require()包裹路径跨域问题 - 确保图片服务器配置CORS纹理未翻转 - 记得设置geometry.scale(1,1,-1)8.2 性能卡顿优化策略降低球体分段数但不要低于32使用低分辨率贴图预览高清图延迟加载启用renderer.outputColorSpace THREE.SRGBColorSpace最后提醒下正式项目建议用PhotoSphereViewer这类成熟库但理解底层原理很重要。我有个项目开始时直接用现成库后来要加特殊效果时不得不重写早知道不如一开始就用Three.js自己实现。