简介:本文详细介绍如何在Vue项目中集成Three.js实现3D物体的缩放动画,涵盖基础场景搭建、动画原理、交互控制及性能优化,适合前端开发者快速掌握3D动画开发技巧。
Three.js作为WebGL的轻量级封装库,与Vue的响应式特性结合可高效实现3D交互。在Vue项目中集成Three.js需考虑组件化开发模式,推荐使用vue-threejs或手动封装Three.js实例为Vue组件。缩放动画适用于产品展示、数据可视化等场景,例如电商平台的3D商品预览或地理信息系统的地形缩放。
npm init vue@latest vue-threejs-demo
npm install three @tweenjs/tween.js
/components/ThreeScene.vue中,动画逻辑通过props/events与父组件通信。
import * as THREE from 'three';export default {data() {return {scene: null,camera: null,renderer: null,mesh: null};},mounted() {this.initScene();this.createBox();this.animate();},methods: {initScene() {// 场景设置this.scene = new THREE.Scene();this.scene.background = new THREE.Color(0xf0f0f0);// 相机配置(透视相机)this.camera = new THREE.PerspectiveCamera(75, // 视野角度window.innerWidth / window.innerHeight, // 宽高比0.1, // 近裁剪面1000 // 远裁剪面);this.camera.position.z = 5;// 渲染器初始化this.renderer = new THREE.WebGLRenderer({ antialias: true });this.renderer.setSize(window.innerWidth, window.innerHeight);this.$refs.container.appendChild(this.renderer.domElement);},createBox() {const geometry = new THREE.BoxGeometry(1, 1, 1);const material = new THREE.MeshBasicMaterial({color: 0x00ff00,wireframe: false});this.mesh = new THREE.Mesh(geometry, material);this.scene.add(this.mesh);}}};
Three.js的动画依赖requestAnimationFrame实现流畅渲染:
animate() {this.animationId = requestAnimationFrame(this.animate);// 更新物体状态(此处预留缩放逻辑)this.renderer.render(this.scene, this.camera);},beforeDestroy() {cancelAnimationFrame(this.animationId);}
使用Three.js的scale属性实现线性缩放:
methods: {startScaling() {const duration = 2000; // 2秒const startTime = Date.now();const animateScale = () => {const elapsed = Date.now() - startTime;const progress = Math.min(elapsed / duration, 1);// 使用缓动函数优化动画效果const easeProgress = this.easeInOutCubic(progress);const scaleFactor = 1 + easeProgress * 2; // 缩放至3倍大小this.mesh.scale.set(scaleFactor, scaleFactor, scaleFactor);if (progress < 1) {requestAnimationFrame(animateScale);}};animateScale();},easeInOutCubic(t) {return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;}}
集成Tween.js实现更复杂的动画序列:
import * as TWEEN from '@tweenjs/tween.js';methods: {tweenScaling() {new TWEEN.Tween(this.mesh.scale).to({ x: 2, y: 2, z: 2 }, 1000) // 目标缩放值和持续时间.easing(TWEEN.Easing.Quadratic.InOut).onUpdate(() => {// 缩放过程中可执行其他操作}).onComplete(() => {console.log('缩放动画完成');}).start();},animate() {requestAnimationFrame(this.animate);TWEEN.update(); // 必须在动画循环中调用this.renderer.render(this.scene, this.camera);}}
mounted() {window.addEventListener('wheel', this.handleWheel);},methods: {handleWheel(e) {e.preventDefault();const delta = e.deltaY > 0 ? 0.9 : 1.1; // 滚轮方向判断this.mesh.scale.multiplyScalar(delta);// 限制缩放范围const currentScale = this.mesh.scale.x;if (currentScale < 0.5 || currentScale > 3) {this.mesh.scale.divideScalar(delta);}}}
安装three/examples/jsm/controls/OrbitControls实现自由旋转查看:
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';methods: {initControls() {this.controls = new OrbitControls(this.camera, this.renderer.domElement);this.controls.enableDamping = true; // 启用阻尼效果this.controls.dampingFactor = 0.05;},animate() {this.controls.update(); // 必须在动画循环中调用// ...其他动画逻辑}}
const lod = new THREE.LOD();lod.addLevel(highDetailMesh, 0);lod.addLevel(lowDetailMesh, 50);
THREE.BufferGeometry替代传统GeometryfrustumCulling裁剪不可见物体this.renderer.setPixelRatio(window.devicePixelRatio)适配高DPI屏幕
<template><div ref="container" class="three-container"></div><button @click="startAnimation">开始缩放</button></template><script>import * as THREE from 'three';import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';import * as TWEEN from '@tweenjs/tween.js';export default {data() {return {scene: null,camera: null,renderer: null,mesh: null,controls: null};},mounted() {this.initThree();this.createCube();this.initControls();this.animate();window.addEventListener('resize', this.onWindowResize);},methods: {initThree() {this.scene = new THREE.Scene();this.camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,1000);this.camera.position.z = 5;this.renderer = new THREE.WebGLRenderer({ antialias: true });this.renderer.setSize(window.innerWidth, window.innerHeight);this.$refs.container.appendChild(this.renderer.domElement);},createCube() {const geometry = new THREE.BoxGeometry();const material = new THREE.MeshPhongMaterial({ color: 0x00ff00 });this.mesh = new THREE.Mesh(geometry, material);this.scene.add(this.mesh);// 添加环境光和方向光const ambientLight = new THREE.AmbientLight(0x404040);this.scene.add(ambientLight);const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);directionalLight.position.set(1, 1, 1);this.scene.add(directionalLight);},initControls() {this.controls = new OrbitControls(this.camera, this.renderer.domElement);this.controls.enableDamping = true;},startAnimation() {new TWEEN.Tween(this.mesh.scale).to({ x: 1.5, y: 1.5, z: 1.5 }, 1000).easing(TWEEN.Easing.Elastic.Out).start();new TWEEN.Tween(this.mesh.position).to({ y: 1 }, 1000).delay(500).start();},onWindowResize() {this.camera.aspect = window.innerWidth / window.innerHeight;this.camera.updateProjectionMatrix();this.renderer.setSize(window.innerWidth, window.innerHeight);},animate() {requestAnimationFrame(this.animate);TWEEN.update();this.controls.update();this.renderer.render(this.scene, this.camera);}},beforeDestroy() {window.removeEventListener('resize', this.onWindowResize);cancelAnimationFrame(this.animationId);if (this.renderer) {this.renderer.dispose();}}};</script><style>.three-container {width: 100%;height: 100vh;overflow: hidden;}</style>
beforeDestroy() {while(this.scene.children.length > 0) {this.scene.remove(this.scene.children[0]);}}
通过以上技术方案,开发者可在Vue项目中高效实现Three.js的物体缩放动画,结合交互控制与性能优化,满足多样化的3D展示需求。实际开发中建议先构建基础场景,再逐步添加动画效果和交互功能,最后进行性能调优。