在上一篇文章中,我們學習了Three.js 坐標系系統與單位理解教程:
Three.js 坐標系系統與單位理解教程
接下來我們要學習的是Three.js 的動畫循環
一、動畫循環基礎原理
1. 什么是動畫循環?
? ? ? ? 動畫循環是連續更新場景狀態并重新渲染的過程,通過快速連續的畫面變化產生動態效果。在Three.js中,這通常以與顯示器刷新率同步的頻率執行。
2. 核心代碼結構
function animate() {requestAnimationFrame(animate); // 1. 請求下一幀updateScene(); // 2. 更新場景狀態renderer.render(scene, camera); // 3. 渲染場景
}
animate(); // 啟動循環
二、Vue3 中的完整實現與解析
1. 組件基礎結構(Composition API)
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import * as THREE from 'three';// DOM 引用
const container = ref(null);// Three.js 核心對象
let scene, camera, renderer, cube;
let animationId = null; // 存儲動畫ID用于取消
const clock = new THREE.Clock(); // Three.js內置時鐘
</script>
2. 初始化函數詳解
function initThreeScene() {// 場景初始化scene = new THREE.Scene();scene.background = new THREE.Color(0x111111);// 相機配置(透視相機)camera = new THREE.PerspectiveCamera(75, // 視野角度(FOV)window.innerWidth/innerHeight, // 寬高比0.1, // 近裁剪面1000 // 遠裁剪面);camera.position.z = 5; // 相機位置// 渲染器配置renderer = new THREE.WebGLRenderer({ antialias: true, // 開啟抗鋸齒alpha: true // 開啟透明度});renderer.setPixelRatio(Math.min(2, window.devicePixelRatio)); // 合理設置像素比renderer.setSize(window.innerWidth, window.innerHeight);container.value.appendChild(renderer.domElement);// 添加測試物體addTestCube();// 啟動動畫循環startAnimationLoop();
}
3. 動畫循環的完整實現
基礎版本:
function animate() {// 遞歸調用形成循環animationId = requestAnimationFrame(animate);// 物體旋轉動畫cube.rotation.x += 0.01;cube.rotation.y += 0.01;// 渲染場景renderer.render(scene, camera);
}
專業版本(帶時間控制):
let previousTime = 0;function animate(currentTime) {animationId = requestAnimationFrame(animate);// 計算時間增量(毫秒轉換為秒)const deltaTime = (currentTime - previousTime) / 1000;previousTime = currentTime;// 使用時間增量確保動畫速度一致const rotationSpeed = 2; // 弧度/秒cube.rotation.x += rotationSpeed * deltaTime;cube.rotation.y += rotationSpeed * deltaTime;// 彈跳動畫(使用正弦函數)const bounceHeight = 0.5;const bounceSpeed = 2;cube.position.y = Math.sin(currentTime * 0.001 * bounceSpeed) * bounceHeight;renderer.render(scene, camera);
}
4. 動畫控制與管理
// 啟動動畫循環
function startAnimationLoop() {if (!animationId) {clock.start(); // 啟動Three.js時鐘previousTime = performance.now(); // 記錄初始時間animate(); // 開始循環}
}// 停止動畫循環
function stopAnimationLoop() {if (animationId) {cancelAnimationFrame(animationId);animationId = null;clock.stop(); // 停止時鐘}
}// 重置動畫狀態
function resetAnimation() {cube.rotation.set(0, 0, 0);cube.position.set(0, 0, 0);previousTime = performance.now(); // 重置時間記錄
}
三、高級動畫技巧
1. 性能優化策略
條件渲染(只在需要時渲染)
let needsUpdate = false;// 當場景變化時調用
function triggerRender() {needsUpdate = true;
}function animate() {animationId = requestAnimationFrame(animate);if (needsUpdate) {renderer.render(scene, camera);needsUpdate = false;}
}
分幀處理(復雜場景優化)
let frameCount = 0;function animate() {animationId = requestAnimationFrame(animate);// 每幀只更新部分元素if (frameCount % 2 === 0) updatePhysics();if (frameCount % 3 === 0) updateBackground();updateMainObjects(); // 主要物體每幀都更新renderer.render(scene, camera);frameCount++;
}
2. 混合動畫技術
function animate() {animationId = requestAnimationFrame(animate);const time = clock.getElapsedTime();// 旋轉動畫cube.rotation.x = time * 1; // 持續旋轉// 脈動動畫(縮放)const pulseSpeed = 3;const pulseIntensity = 0.2;cube.scale.setScalar(1 + Math.sin(time * pulseSpeed) * pulseIntensity);// 顏色變化cube.material.color.setHSL(Math.sin(time * 0.5) * 0.5 + 0.5, // 色相 (0-1)0.8, // 飽和度0.5 // 亮度);renderer.render(scene, camera);
}
四、Vue3 組件完整實現
<template><!-- 容器用于掛載 Three.js 渲染器 --><div ref="container" class="three-container"></div><!-- 控制面板,包含播放/暫停、重置按鈕和 FPS 顯示 --><div class="control-panel"><!-- 播放/暫停按鈕,點擊切換動畫狀態 --><button @click="toggleAnimation">{{ isPlaying ? '? 暫停' : '? 播放' }}</button><!-- 重置動畫按鈕 --><button @click="resetAnimation">? 重置</button><!-- 顯示當前 FPS 值 --><span class="fps-counter">FPS: {{ fps.toFixed(1) }}</span></div>
</template><script setup>
// 導入 Vue 的響應式 API
import { ref, onMounted, onBeforeUnmount } from 'vue';
// 導入 Three.js 庫
import * as THREE from 'three';// 創建響應式引用:容器 DOM 元素
const container = ref(null);
// 創建響應式引用:動畫播放狀態
const isPlaying = ref(true);
// 創建響應式引用:FPS 值
const fps = ref(0);// 聲明 Three.js 相關對象變量
let scene, camera, renderer, cube;
// 動畫幀 ID,用于取消動畫循環
let animationId = null;
// Three.js 時鐘對象,用于計算時間差
const clock = new THREE.Clock();// FPS 計算相關變量
let lastFpsUpdate = 0; // 上次更新 FPS 的時間戳
let frameCount = 0; // 幀計數器// 初始化 Three.js 場景
function initScene() {// 創建場景對象scene = new THREE.Scene();// 設置場景背景顏色scene.background = new THREE.Color(0x222222);// 創建透視相機camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 100);// 設置相機位置camera.position.set(0, 1, 5);// 相機看向原點camera.lookAt(0, 0, 0);// 創建 WebGL 渲染器,開啟抗鋸齒renderer = new THREE.WebGLRenderer({ antialias: true });// 設置渲染器像素比率renderer.setPixelRatio(window.devicePixelRatio);// 設置渲染器尺寸renderer.setSize(window.innerWidth, window.innerHeight);// 將渲染器的 DOM 元素添加到容器中container.value.appendChild(renderer.domElement);// 創建環境光const ambientLight = new THREE.AmbientLight(0x404040);// 創建方向光const directionalLight = new THREE.DirectionalLight(0xffffff, 1);// 設置方向光位置directionalLight.position.set(1, 1, 1);// 將燈光添加到場景中scene.add(ambientLight, directionalLight);// 添加動畫立方體到場景addAnimatedCube();// 添加坐標軸輔助器scene.add(new THREE.AxesHelper(3));// 添加網格輔助器scene.add(new THREE.GridHelper(10, 10));// 開始動畫循環startAnimation();
}// 創建并添加動畫立方體
function addAnimatedCube() {// 創建立方體幾何體const geometry = new THREE.BoxGeometry(1, 1, 1);// 創建標準材質并設置屬性const material = new THREE.MeshStandardMaterial({ color: 0x00ff00, // 顏色roughness: 0.3, // 粗糙度metalness: 0.7 // 金屬度});// 創建網格對象cube = new THREE.Mesh(geometry, material);// 將立方體添加到場景中scene.add(cube);
}// 動畫循環函數
function animate(timestamp) {// 請求下一幀動畫animationId = requestAnimationFrame(animate);// 更新 FPS 計數器updateFpsCounter(timestamp);// 獲取時間差和總時間const delta = clock.getDelta();const elapsed = clock.getElapsedTime();// 更新動畫狀態updateAnimations(elapsed, delta);// 渲染場景renderer.render(scene, camera);
}// 更新所有動畫效果
function updateAnimations(time, delta) {// 立方體繞 X 軸旋轉cube.rotation.x = time * 0.5;// 立方體繞 Y 軸旋轉cube.rotation.y = time * 0.3;// 立方體上下彈跳效果cube.position.y = Math.sin(time * 2) * 0.5;// 立方體顏色隨時間變化cube.material.color.setHSL((Math.sin(time * 0.3) + 1) / 2, // 色相0.8, // 飽和度0.6 // 亮度);
}// 更新 FPS 計數器
function updateFpsCounter(timestamp) {// 幀數遞增frameCount++;// 每秒更新一次 FPS 值if (timestamp >= lastFpsUpdate + 1000) {fps.value = (frameCount * 1000) / (timestamp - lastFpsUpdate);// 重置幀計數器和更新時間frameCount = 0;lastFpsUpdate = timestamp;}
}// 控制動畫播放的方法
function startAnimation() {// 如果動畫未運行則開始動畫if (!animationId) {clock.start();animationId = requestAnimationFrame(animate);isPlaying.value = true;}
}// 停止動畫
function stopAnimation() {// 如果動畫正在運行則停止if (animationId) {cancelAnimationFrame(animationId);animationId = null;clock.stop();isPlaying.value = false;}
}// 切換動畫播放狀態
function toggleAnimation() {if (isPlaying.value) stopAnimation();else startAnimation();
}// 重置動畫狀態
function resetAnimation() {// 重置立方體旋轉cube.rotation.set(0, 0, 0);// 重置立方體位置cube.position.set(0, 0, 0);// 重置立方體縮放cube.scale.set(1, 1, 1);// 重置立方體顏色cube.material.color.set(0x00ff00);// 重啟時鐘clock.start();
}// 窗口大小調整處理函數
function onResize() {// 更新相機寬高比camera.aspect = window.innerWidth / window.innerHeight;// 更新相機投影矩陣camera.updateProjectionMatrix();// 更新渲染器尺寸renderer.setSize(window.innerWidth, window.innerHeight);
}// 組件掛載時執行
onMounted(() => {// 初始化場景initScene();// 添加窗口大小調整事件監聽器window.addEventListener('resize', onResize);
});// 組件卸載前執行清理工作
onBeforeUnmount(() => {// 停止動畫stopAnimation();// 移除窗口大小調整事件監聽器window.removeEventListener('resize', onResize);// 釋放渲染器資源if (renderer) {renderer.dispose();renderer.forceContextLoss();}
});
</script><style>
/* Three.js 容器樣式 */
.three-container {position: fixed;top: 0;left: 0;width: 100%;height: 100%;outline: none;
}/* 控制面板樣式 */
.control-panel {position: fixed;bottom: 20px;left: 50%;transform: translateX(-50%);display: flex;gap: 10px;align-items: center;background: rgba(0, 0, 0, 0.7);padding: 10px 15px;border-radius: 20px;color: white;
}/* 控制面板按鈕樣式 */
.control-panel button {background: rgba(255, 255, 255, 0.1);border: 1px solid rgba(255, 255, 255, 0.3);color: white;padding: 5px 12px;border-radius: 15px;cursor: pointer;transition: all 0.2s;
}/* 按鈕懸停效果 */
.control-panel button:hover {background: rgba(255, 255, 255, 0.2);
}/* FPS 計數器樣式 */
.fps-counter {font-family: monospace;min-width: 80px;display: inline-block;text-align: center;
}
</style>
實現效果
three.js動畫
五、關鍵知識點總結
-
動畫循環三要素:
-
requestAnimationFrame
?遞歸調用 -
場景狀態更新
-
渲染器執行渲染
-
-
時間控制的重要性:
-
使用?
performance.now()
?或?THREE.Clock
-
通過 delta time 確保動畫速度一致
-
-
性能優化方向:
-
條件渲染(只在需要時渲染)
-
分幀處理(復雜場景優化)
-
合理使用?
dispose()
?釋放資源
-
-
Vue3 集成要點:
-
在?
onMounted
?中初始化 -
在?
onBeforeUnmount
?中清理 -
使用 ref 管理DOM元素引用
-
-
調試技巧:
-
添加FPS計數器監控性能
-
使用坐標輔助器查看空間關系
-
實現動畫控制按鈕方便調試
-
? ? ? ?這個實現展示了專業級的Three.js動畫循環管理,包含了性能優化、時間控制、狀態管理等關鍵要素,可以直接用于生產環境項目。