目录
1. 浏览器渲染机制底层原理
1.1 JavaScript事件循环与渲染帧
宏任务/微任务对动画的影响
1.2 帧率控制科学
Delta Time标准化计算:
帧率波动补偿策略:
2. Three.js动画体系深度解析
2.1 原生动画循环优化
性能黑洞检测:
渲染节流技术:
2.2 补间动画工业级解决方案
2.2.1 Tween.js源码级剖析
2.2.2 高级路径动画
2.3 动画系统架构设计
状态机管理:
动画轨道系统:
动画混合与层叠
性能优化:
本篇文章适合基本掌握three.js基础知识的开发者,所以本文不过于重复之前的内容,但是也会适当回顾关键概念
1. 浏览器渲染机制底层原理
1.1 JavaScript事件循环与渲染帧
宏任务/微任务对动画的影响
setTimeout vs requestAnimationFrame(rAF):
setTimeout是基于事件循环的宏任务调度,无法保证精确的动画时间间隔。
requestAnimationFrame是专门为动画设计的API,与浏览器的渲染管线同步,能够提供更加平滑的动画效果。
浏览器渲染管线
解析 → 样式计算 → 布局 → 绘制 → 合成
以上步骤是浏览器渲染页面的关键流程
垂直同步(VSync)
rAF
如何与显示器刷新率同步(60Hz/120Hz):rAF
通常会尝试与显示器的刷新率同步,以减少画面撕裂和卡顿。
1.2 帧率控制科学
Delta Time标准化计算:
const clock = new THREE.Clock();
let delta = 0;
const targetFPS = 60;function animate() {delta = clock.getDelta(); // 获取精确到毫秒的时间差// 基于实际耗时计算运动增量cube.rotation.y += (Math.PI / 2) * delta; // 每秒旋转90度requestAnimationFrame(animate);
}
帧率波动补偿策略:
当delta > 0.1
时启用插值算法来平滑动画
丢帧处理:使用累积时间算法避免动画跳跃
let accumulator = 0;
const fixedStep = 1 / 60; // 固定步长function animate() {accumulator += clock.getDelta();while (accumulator >= fixedStep) {updatePhysics(fixedStep); // 物理模拟需固定步长accumulator -= fixedStep;}render(accumulator / fixedStep); // 插值渲染
}
2. Three.js动画体系深度解析
2.1 原生动画循环优化
性能黑洞检测:
在动画循环中创建对象(如new THREE.Vector3()
)
频繁修改几何体顶点数据(应使用geometry.verticesNeedUpdate = true
标记)
渲染节流技术:
通过setTimeout
实现非活动状态降频渲染
let isActive = true;function conditionalAnimate() {if (isActive) {animate();setTimeout(conditionalAnimate, 1000 / 30); // 降为30FPS}
}
2.2 补间动画工业级解决方案
2.2.1 Tween.js源码级剖析
时间函数算法实现:
自定义三次贝塞尔缓动函数
function cubicBezier(t, p1x, p1y, p2x, p2y) {// 详细实现贝塞尔公式...
}
对象池优化:
复用Tween实例避免垃圾回收(GC)压力
const tweenPool = [];function createTween(target) {return tweenPool.length > 0 ? tweenPool.pop().reset(target): new TWEEN.Tween(target);
}
2.2.2 高级路径动画
贝塞尔曲线运动:
const curve = new THREE.CubicBezierCurve3(new THREE.Vector3(0, 0, 0),new THREE.Vector3(2, 3, 1),new THREE.Vector3(-1, 2, 0),new THREE.Vector3(5, 0, 3)
);new TWEEN.Tween({ t: 0 }).to({ t: 1 }, 3000).onUpdate(({ t }) => {const point = curve.getPoint(t);object.position.copy(point);}).start();
跟随路径旋转:
使用curve.getTangent(t)
计算朝向
2.3 动画系统架构设计
状态机管理:
定义IDLE
, PLAYING
, PAUSED
等状态,用于管理动画的生命周期
动画轨道系统:
通过THREE.AnimationClip
实现多轨道融合
const clip = new THREE.AnimationClip('dance', 10, [new THREE.VectorKeyframeTrack('.position', [0, 3, 6], [0,0,0, 2,1,0, 2,1,3]),new THREE.QuaternionKeyframeTrack('.quaternion',[0, 5],[0,0,0,1, 0.707,0,0.707,0] // 绕Y轴旋转180度)
]);const mixer = new THREE.AnimationMixer(model);
const action = mixer.clipAction(clip);
action.play();
动画混合与层叠
使用 THREE.AnimationMixer
和多个 THREE.AnimationClip
实现复杂的动画混合效果。
通过调整 AnimationMixer
的权重来控制不同动画的混合比例。
案例:
性能优化:
避免不必要的渲染和计算,使用 requestAnimationFrame
进行动画循环。
案例:
// 初始化场景、相机和渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);// 加载一个带有多个动画的GLTF模型
const loader = new THREE.GLTFLoader();
loader.load('path/to/your/model.glb', function (gltf) {const model = gltf.scene;scene.add(model);// 创建AnimationMixerconst mixer = new THREE.AnimationMixer(model);// 获取动画剪辑const clipWalk = gltf.animations[0];const clipRun = gltf.animations[1];// 创建动画动作const actionWalk = mixer.clipAction(clipWalk);const actionRun = mixer.clipAction(clipRun);// 播放动画并设置权重actionWalk.play();actionRun.play();// 初始化权重let walkWeight = 1.0;let runWeight = 0.0;// 动画循环function animate() {requestAnimationFrame(animate);// 更新权重(这里只是示例,你可以根据需求动态调整权重)walkWeight = Math.cos(Date.now() * 0.001) * 0.5 + 0.5;runWeight = 1.0 - walkWeight;// 设置权重actionWalk.setWeight(walkWeight);actionRun.setWeight(runWeight);// 更新mixermixer.update(0.01);// 渲染场景renderer.render(scene, camera);}animate();
}, undefined, function (error) {console.error(error);
});// 设置相机位置
camera.position.z = 5;
尽量减少对DOM的操作,使用CSS动画或WebGL进行高效的渲染。
利用GPU加速,减少CPU的负担。
案例:
// 创建一个几何体和材质
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });// 创建一个InstancedMesh
const count = 1000;
const instancedMesh = new THREE.InstancedMesh(geometry, material, count);// 设置每个实例的变换矩阵
for (let i = 0; i < count; i++) {const matrix = new THREE.Matrix4().makeTranslation(Math.random() * 10 - 5,Math.random() * 10 - 5,Math.random() * 10 - 5);instancedMesh.setMatrixAt(i, matrix);
}// 更新InstancedMesh的变换矩阵
instancedMesh.instanceMatrix.needsUpdate = true;// 将InstancedMesh添加到场景中
scene.add(instancedMesh);// 动画循环
function animate() {requestAnimationFrame(animate);// 更新每个实例的变换(这里只是简单的旋转示例)for (let i = 0; i < count; i++) {const matrix = instancedMesh.getMatrixAt(i);matrix.premultiply(new THREE.Matrix4().makeRotationY(0.01));instancedMesh.setMatrixAt(i, matrix);}instancedMesh.instanceMatrix.needsUpdate = true;// 渲染场景renderer.render(scene, camera);
}
animate();
码字不易,各位大佬点点赞呗