关键帧与编排:途经点、时间轴、交错
一段动画最小的形态是一对 from 与
to。把它往三个方向推广,就得到编排所需的全部要素:值可以是一串而不止两个(关键帧),动画可以是多段而不止一段(时间线),元素可以是一组而不止一个(交错)。三者互不重叠,组合起来足以描述绝大多数界面动效。
1 · 关键帧与 times
keyframes: [0, 0.9, 0.3, 1] 给出四个途经值,引擎在相邻两值之间分段插值。真正让关键帧好用的是 times:它另给一串
上的位置,指定每个值出现在整段进度的哪一处。
值与位置就此解耦。同一串途经值配不同的 times,节奏完全不同:把 times 写成 [0, 0.2, 0.8, 1],中间那段占了 60% 的时间却只从 0.9 挪到 0.3,看上去像是在慢悠悠地折返;而首尾两段各只有 20% 的时间要走完 0.9 的位移,快得多。
2 · 交错
stagger(each, { from }) 返回一个
的函数,不产生动画,只批量算延迟。延迟正比于该元素到发起点的距离:,其中 ratio 是归一化距离,可再过一层 ease 改变疏密。
注 · from: 'center' 在偶数个元素时发起点落在
这个半整数上,中间两个元素到它的距离相同、延迟相同,波看上去是从两个格子之间发出的。奇数个则有一个明确的波心。图 2-1 用 9 个点正是为此。
3 · 时间线
timeline 把多段动画排到一条轴上,每段的 at 决定它的起点:省略表示接在上一段结束处,'<' 表示与上一段同时起,'+=100' 表示上一段结束后再等 100 ms,也可以直接给绝对毫秒数。排布在建立时一次算定,entries 里每段都带上算好的 start 与
end。
关键在于时间线本身也是一个动画,它的「值」就是全局时间。因此 play / pause / seek / reverse 这些能力不需要为编排单独实现一遍——把主时钟的值分发给各段,各段用 time - start 取自己的局部时间即可。
import { Animation, timeline, stagger, animate } from '@vega/anim';
// 关键帧 + times: 一串途经值, times 指定各值出现的进度
new Animation({
keyframes: [0, 0.9, 0.3, 1],
times: [0, 0.2, 0.8, 1], // 0.2→0.8 占 60% 时间, 只缓行一小段
duration: 2600, ease: 'linear',
});
// 交错: stagger 返回 (index, total) → delay
const delayFor = stagger(80, { from: 'center' });
dots.forEach((d, i) =>
animate(d, { y: [0, -26, 0] }, { duration: 520, delay: delayFor(i, N) }));
// 时间线: 各段排在一条轴上, at 控制相对位置
timeline([
{ animation: a },
{ animation: b, at: '+=100' }, // A 结束后晚 100ms 起
{ animation: c, at: '<' }, // 与 B 同时起
]);
每个途经值之间怎么插出来见插值,每段的快慢由缓动塑形。序列、运行时依赖与二维交错见编排进阶;整条时间线如何被一个可反向的游标驱动见播放控制。