Skip to content

Three.js - 动画

静态 3D 场景虽然有趣,但动画才是真正赋予它们生命力的关键。在 Three.js 中,动画是通过创建渲染循环 (render loop) 来实现的,该循环在每一帧更新场景并重新渲染,从而产生运动的错觉。

使用 requestAnimationFrame 的渲染循环

Section titled “使用 requestAnimationFrame 的渲染循环”

创建高效动画循环的标准、浏览器原生方法是使用 window.requestAnimationFrame() 方法。它告诉浏览器您希望执行动画,并请求浏览器在下一次重绘之前调用一个指定的函数来更新动画。这比使用 setInterval 效率高得多,因为它允许浏览器优化渲染,在非活动标签页中暂停动画,并与显示器的刷新率同步。

function tick() {
// 在这里放置更新场景中对象的代码
// 渲染场景
renderer.render(scene, camera);
// 安排下一帧
window.requestAnimationFrame(tick);
}
// 启动循环
tick();

一个常见的错误是在每一帧中以固定量更新对象的属性,例如 cube.rotation.y += 0.01;。这会产生一个问题:动画在高刷新率显示器(例如 144Hz)上会运行得更快,而在标准显示器(60Hz)上则会更慢。

为了解决这个问题,我们必须使用帧之间经过的时间来计算更新。Three.js 为此提供了一个便捷的 THREE.Clock 工具。

import * as THREE from 'three';
const clock = new THREE.Clock();
function tick() {
// 获取自上一帧以来经过的时间
const deltaTime = clock.getDelta();
// 获取自时钟启动以来经过的总时间
const elapsedTime = clock.getElapsedTime();
// 根据经过时间更新对象旋转,以实现平滑、一致的速度
// cube.rotation.y = elapsedTime * Math.PI; // 每秒旋转 180 度
// 使对象沿圆形路径移动
cube.position.x = Math.cos(elapsedTime) * 5;
cube.position.z = Math.sin(elapsedTime) * 5;
renderer.render(scene, camera);
window.requestAnimationFrame(tick);
}
tick();

使用补间 (Tweening) 库实现高级动画

Section titled “使用补间 (Tweening) 库实现高级动画”

对于更复杂的、基于时间线的动画(例如,在 2 秒内将对象从 A 点移动到 B 点并带有特定的缓动效果),专用的补间库 (tweening library) 是无价的。虽然原始教程使用了 Tween.js,但一个非常流行且强大的行业标准是 GSAP (GreenSock Animation Platform)。它以其性能、灵活性和出色的文档而闻名。

让我们看看如何将 GSAP 集成到我们的项目中。

npm install gsap

GSAP 可以直接动画任何 JavaScript 对象的属性,包括 Three.js 对象,例如 mesh.position、mesh.rotation 或 material.color。

import gsap from 'gsap';
// 在 1 秒内动画立方体的 Y 轴位置
gsap.to(cube.position, { duration: 1, delay: 0.5, y: 5 });
// 以弹性缓动效果向下动画
gsap.to(cube.position, { duration: 1.5, delay: 1.5, y: 0, ease: 'elastic.out(1, 0.3)' });

GSAP 的一个主要优点是您不需要在渲染循环中调用更新函数。它管理自己的内部计时器,使动画代码非常简洁和声明式。

  • 性能:避免在渲染循环内部创建新对象(如 new THREE.Vector3())或执行大量计算。尽可能在循环外部完成设置。
  • 调试:使用 console.log(elapsedTime) 检查时间,或使用 stats.js 库监控场景的帧率 (FPS)。
  • 控制:对于复杂的序列动画,可以使用 GSAP 等库;但对于连续的、程序性的动画,如波浪、漂浮或恒定旋转,则应利用 tick 函数和 THREE.Clock。
  • 用户体验:如果用户对动画敏感,应始终提供暂停或减少动画的方式。考虑使用 (prefers-reduced-motion) CSS 媒体查询来默认禁用非必要的动画。

实践这些概念的一个好方法是构建一个简单的太阳系。

  1. 为太阳创建一个球体,并为行星创建几个较小的球体。
  2. 在 tick 函数中,使用 elapsedTime 和 Math.sin() 和 Math.cos() 使行星以不同的速度和距离绕太阳公转。
  3. 使用 planet.rotation.y = elapsedTime * speed; 使行星围绕自己的轴旋转。
  4. 挑战:当用户点击某个行星时,使用 GSAP 动画摄像机,使其从广角视图移动并聚焦到该行星。