两种动画思路
代码驱动:每帧根据时间计算属性值,例如 ball.position.x = f(t)。灵活、适合程序化运动。关键帧驱动:只描述几个时刻的值,中间由引擎插值,适合设计好的动作,也是 glTF 模型动画的格式。本示例右边的小球是前者,左边的方块是后者。
时间从哪来
THREE.Timer 替代了已废弃的 Clock。每帧调用 timer.update(time) 后:
timer.getDelta():距上一帧的秒数,用于“每秒移动多少”的增量运动;timer.getElapsed():从开始到现在的总秒数,用于周期运动。
用时间而不是帧数做动画,在 60Hz 和 144Hz 的屏幕上速度才一致。
缓动(Easing)
先把时间归一化成 0–1 的进度 t,再用缓动函数改变它的“节奏”,最后用 lerp(start, end, e) 得到位置:
js
const e = easeOutBack(t);
ball.position.x = THREE.MathUtils.lerp(x0, x1, e);linear 匀速;easeInOutCubic 慢起慢停;easeOutBack 先冲过头再回来;elastic 像弹簧;bounce 像落地弹跳。五条轨道并排,同一时刻出发、同一时刻到达,只是过程不同。
关键帧系统的四层结构
- KeyframeTrack:一个属性路径 + 时间数组 + 值数组。
VectorKeyframeTrack(".position", times, values),值数组是扁平的(每个关键帧 3 个数)。旋转用QuaternionKeyframeTrack,颜色用ColorKeyframeTrack,路径还可以深入子属性,如".material.color"。 - AnimationClip:把多条轨道打包成一个有名字、有时长的片段。
- AnimationMixer:绑定到一个根对象,负责按名字找到属性并写入插值结果。
- AnimationAction:
mixer.clipAction(clip)返回的播放控制器,可以play()、暂停、设置循环模式setLoop、调速,或用crossFadeTo在两个动作之间平滑过渡。
每帧必须调用 mixer.update(delta),否则动画不会前进。
四元数插值总是走最短路径,所以一整圈旋转要至少拆成三段,每段小于 180°。
核心 API
THREE.TimerTHREE.MathUtils.lerpTHREE.VectorKeyframeTrackTHREE.QuaternionKeyframeTrackTHREE.ColorKeyframeTrackTHREE.AnimationClipTHREE.AnimationMixerAnimationAction.crossFadeToTHREE.LoopPingPong动手试试
- 1自己写一个 easeInQuad:t => t * t,加入 easings 对象,看看新轨道。
- 2把 timeScale 调成负数,让关键帧动画倒着播放。
- 3给 hop 片段加一条 NumberKeyframeTrack(".material.emissiveIntensity", ...),让方块落地时闪光。
- 4把 loop 设为 once,观察 clampWhenFinished 如何让方块停在最后一帧。
还没有输出。console.log 的内容会显示在这里。
运行中