用Three.js在Vue项目里搞个能点选的3D集装箱,从光源到交互完整走一遍

发布时间:2026/6/11 9:24:41

用Three.js在Vue项目里搞个能点选的3D集装箱,从光源到交互完整走一遍 在Vue中构建可交互3D集装箱Three.js全流程实战指南当物流管理系统需要展示集装箱堆场状态或是电商平台想要呈现商品在集装箱内的立体分布时3D可视化技术就能大显身手。本文将带你在Vue项目中用Three.js打造一个带有点选交互功能的3D集装箱组件。不同于基础教程的API罗列我们会重点解决Vue组件化开发中的实际问题——如何让3D场景与Vue的响应式系统和谐共处以及如何实现专业级的交互体验。1. 环境搭建与基础架构1.1 项目初始化与依赖安装首先确保你的Vue项目已经配置好推荐使用Vue 3 Vite。通过以下命令安装核心依赖npm install three types/three three-stdlib关键依赖说明threeThree.js核心库types/threeTypeScript类型定义three-stdlib包含OrbitControls等扩展工具1.2 Vue组件基础结构创建Container3D.vue组件采用Composition API组织代码template div refcontainer classthree-container/div /template script setup import { ref, onMounted, onBeforeUnmount } from vue import * as THREE from three import { OrbitControls } from three/addons/controls/OrbitControls.js const container ref(null) // 其他状态和函数将在后续步骤中添加 /script style scoped .three-container { width: 100%; height: 100vh; background: #f0f2f5; } /style2. 三维场景核心要素构建2.1 场景初始化三部曲在onMounted生命周期中构建基础场景const scene new THREE.Scene() scene.background new THREE.Color(0x09152b) // 透视摄像机75度视野适配容器宽高比 const camera new THREE.PerspectiveCamera( 75, container.value.clientWidth / container.value.clientHeight, 0.1, 1000 ) camera.position.set(5, 5, 5) // WebGL渲染器配置 const renderer new THREE.WebGLRenderer({ antialias: true }) renderer.setSize(container.value.clientWidth, container.value.clientHeight) container.value.appendChild(renderer.domElement)2.2 智能光源系统设计合理的灯光配置能让集装箱材质更逼真function initLights() { // 环境光基础照明 const ambientLight new THREE.AmbientLight(0xffffff, 0.6) scene.add(ambientLight) // 平行光模拟日光 const directionalLight new THREE.DirectionalLight(0xffffff, 0.8) directionalLight.position.set(10, 20, 10) scene.add(directionalLight) // 点光源局部高光 const pointLight new THREE.PointLight(0xffffff, 30, 50) pointLight.position.set(5, 10, 5) scene.add(pointLight) }提示使用THREE.RectAreaLight可以创建更真实的平面光源效果特别适合展示集装箱内部空间3. 集装箱模型高级实现3.1 带纹理的集装箱建模创建具有真实质感的集装箱模型async function createContainer() { // 加载纹理建议使用4096x4096的高清贴图 const textureLoader new THREE.TextureLoader() const [sideTexture, topTexture] await Promise.all([ textureLoader.loadAsync(/textures/container_side.jpg), textureLoader.loadAsync(/textures/container_top.jpg) ]) // 主箱体标准20尺集装箱尺寸 const geometry new THREE.BoxGeometry(6, 2.6, 2.4) const material new THREE.MeshPhongMaterial({ map: sideTexture, shininess: 30 }) const container new THREE.Mesh(geometry, material) scene.add(container) // 箱顶使用不同纹理 const topGeometry new THREE.PlaneGeometry(6, 2.4) const topMaterial new THREE.MeshStandardMaterial({ map: topTexture, roughness: 0.7 }) const top new THREE.Mesh(topGeometry, topMaterial) top.rotation.x -Math.PI / 2 top.position.y 2.6 scene.add(top) return container }3.2 性能优化技巧对于复杂场景这些优化手段很关键合并几何体使用THREE.BufferGeometryUtils.mergeBufferGeometries实例化渲染THREE.InstancedMesh适合相同模型的多个实例LOD细节层级THREE.LOD根据距离切换不同精度模型4. 交互系统深度开发4.1 相机控制器集成添加OrbitControls并解决常见问题const controls new OrbitControls(camera, renderer.domElement) controls.enableDamping true controls.dampingFactor 0.05 // 解决Vue热更新时的控制器残留问题 onBeforeUnmount(() { controls.dispose() })4.2 射线检测交互实现点击选中集装箱并高亮显示const raycaster new THREE.Raycaster() const mouse new THREE.Vector2() let selectedObj null function onMouseClick(event) { // 转换鼠标坐标到标准化设备坐标 mouse.x (event.clientX / window.innerWidth) * 2 - 1 mouse.y -(event.clientY / window.innerHeight) * 2 1 // 更新射线 raycaster.setFromCamera(mouse, camera) // 检测相交物体 const intersects raycaster.intersectObjects(scene.children) if (intersects.length 0) { // 移除之前选中效果 if (selectedObj) { selectedObj.material.emissive.setHex(selectedObj.userData.originalEmissive) } // 设置新选中效果 selectedObj intersects[0].object selectedObj.userData.originalEmissive selectedObj.material.emissive.getHex() selectedObj.material.emissive.setHex(0xff0000) // 触发Vue组件事件 emit(container-selected, selectedObj.userData) } } onMounted(() { window.addEventListener(click, onMouseClick) })5. Vue集成最佳实践5.1 响应式数据绑定将Three.js对象与Vue响应式系统结合import { reactive } from vue const state reactive({ containers: [], selectedContainer: null }) async function loadContainers() { const positions [ { x: 0, y: 0, z: 0 }, { x: 8, y: 0, z: 0 }, { x: 0, y: 0, z: 4 } ] state.containers await Promise.all( positions.map(pos createContainerAtPosition(pos)) ) } function createContainerAtPosition(position) { const container createContainer() container.position.set(position.x, position.y, position.z) container.userData { id: generateUUID(), ...position } return container }5.2 自适应布局处理响应窗口大小变化的解决方案function onWindowResize() { camera.aspect container.value.clientWidth / container.value.clientHeight camera.updateProjectionMatrix() renderer.setSize(container.value.clientWidth, container.value.clientHeight) } onMounted(() { window.addEventListener(resize, onWindowResize) }) onBeforeUnmount(() { window.removeEventListener(resize, onWindowResize) })6. 高级功能扩展6.1 集装箱门开闭动画通过GSAP实现流畅的开门动画import gsap from gsap function animateDoor(container, open true) { const door container.getObjectByName(door) if (!door) return gsap.to(door.rotation, { y: open ? Math.PI/2 : 0, duration: 1, ease: power2.inOut }) }6.2 WebXR集成添加VR查看支持import { VRButton } from three/addons/webxr/VRButton.js function initXR() { renderer.xr.enabled true document.body.appendChild(VRButton.createButton(renderer)) renderer.setAnimationLoop(() { renderer.render(scene, camera) }) }在真实项目中集装箱的角件、门锁等细节都需要专门建模。我曾在一个物流系统中需要展示20个集装箱的堆叠状态发现使用实例化渲染后性能提升了300%。当鼠标悬停在某个集装箱上时会显示其装载的货物清单——这只需要在raycaster检测时添加pointermove事件处理即可。

相关新闻