Animation flow
Motion Canvas 使用生成器函数来描述动画。
生成器函数是一个可以返回多个值的函数:
function* example() {
yield 1;
yield 2;
yield 3;
}
const generator = example();
console.log(generator.next().value); // 1;
console.log(generator.next().value); // 2;
console.log(generator.next().value); // 3;
当遇到 yield 关键字时,函数的执行会暂停,只有在调用者请求另一个值时才会恢复。这在声明动画时特别有用 - 通常我们希望以渐进的方式更改屏幕上的事物以创建运动的错觉。我们还希望在这些更新之间 等待恒定的时间,以便我们的眼睛能够注册正在发生的事情。通过生成器,我们可以在 yield 关键字之间更新事物,然后在函数 yield 时等待一段时间。
这是 Motion Canvas 的基本思想。yield 意味着:"当前帧已准备好,将其显示在屏幕上,稍后再回来找我。"
考虑到这一点,我们可以使用以下代码使圆圈在屏幕上闪烁:
export default makeScene2D(function* (view) {
const circle = createRef<Circle>();
view.add(<Circle ref={circle} width={100} height={100} />);
circle().fill('red');
yield;
circle().fill('blue');
yield;
circle().fill('red');
yield;
});
不用说,如果我们必须这样编写所有动画,那将是非常麻烦的。幸运的是,JavaScript 有另一个关键字用于在生成器中使用 - yield*。它允许我们将 yield 委托给另一个生成器。
例如,我们可以从上面的示例中提取闪烁代码到一个单独的生成器,并将我们的场景函数委托给它:
import {createRef, ThreadGenerator} from '@motion-canvas/core';
import {makeScene2D, Circle} from '@motion-canvas/2d';
export default makeScene2D(function* (view) {
const circle = createRef<Circle>();
view.add(<Circle ref={circle} width={100} height={100} />);
yield* flicker(circle());
});
function* flicker(circle: Circle): ThreadGenerator {
circle.fill('red');
yield;
circle.fill('blue');
yield;
circle.fill('red');
yield;
}
生成的动画完全相同,但现在我们有了一个可重用的函数,可以在需要闪烁时使用。
Motion Canvas 提供 了许多像这样的有用生成器。你可能还记得 quickstart 中的这个片段:
yield * myCircle().fill('#e6a700', 1);
它在一秒钟的时间内将圆圈的填充颜色从当前值动画到 #e6a700。你可以猜到,调用 fill('#e6a700', 1) 的结果是另一个生成器,我们可以将场景函数重定向到它。这样的生成器称为 tweens,因为它们在两个值之间动画。你可以在 tweening 部分阅读更多相关信息。
Flow Generators
另一种生成器是 flow generators。它们接受一个或多个生成器作为输入并将它们组合在一起。我们在快速入门部分提到了 all() 生成器,还有几个:
all
API 文档
any
API 文档
chain
API 文档
delay
API 文档
sequence
API 文档
loop
API 文档
Looping
有多种方法可以为多个对象制作动画。这里有一些示例。尝试在下方的编辑器中使用它们。
Click to preview animation
import {makeScene2D, Rect} from '@motion-canvas/2d';
import {all, waitFor, makeRef, range} from '@motion-canvas/core';
export default makeScene2D(function* (view) {
const rects: Rect[] = [];
// 创建一些矩形
view.add(
range(5).map(i => (
<Rect
ref={makeRef(rects, i)}
width={100}
height={100}
x={-250 + 125 * i}
fill="#88C0D0"
radius={10}
/>
)),
);
yield* waitFor(1);
// 为它们制作动画
yield* all(
...rects.map(rect => rect.position.y(100, 1).to(-100, 2).to(0, 1)),
);
});
Using Array.map and all
这是执行简单 tween 的最优雅方法之一,但需要嵌套 all 来对对象执行多个 tween,因为 map 回调必须返回 ThreadGenerator。
yield *
all(
...rects.map(rect =>
// 没有 yield 或任何东西;我们返回这个生成器并在外部处理它
rect.position.y(100, 1).to(-100, 2).to(0, 1),
),
);
Using a for loop and all
这与上面类似,但使用 for 循环和生成器数组。
const generators = [];
for (const rect of rects) {
// 这里没有 yield,只是存储生成器。
generators.push(rect.position.y(100, 1).to(-100, 2).to(0, 1));
}
// 运行所有生成器。
yield * all(...generators);
Using a for loop
这有点麻烦,因为你必须弄清楚循环中的生成器需要多长时间才能完成,但在某些情况下很有用。
for (const rect of rects) {
// 注意这个 yield 后面没有 *
yield rect.position.y(100, 1).to(-100, 2).to(0, 1);
}
// 等待上述生成器的持续时间
yield * waitFor(4);