信号
信号表示一个可能会随时间变化的值。它们可用于定义动画状态之间的依赖关系。这样,当值发生更改时,依赖于它的所有其他值都会自动更新。
概述
基元类型的信号是使用
createSignal()
函数,其中第一个参数指定其初始值:
import {createSignal} from '@motion-canvas/core';
const signal = createSignal(0);
此外,每个复杂类型都有一个静态 createSignal()
方法,可用于为所述类型创建信号:
import {Vector2} from '@motion-canvas/core';
const signal = Vector2.createSignal(Vector2.up);
每个节点的属性也由 signals 表示:
const circle = <Circle />;
const signal = circle.fill;
创建后,可以调用信号来执行以下三种可能的操作之一(根据参数的数量选择操作):
- 检索值:
const value = signal();
- 更新值:
signal(3);
为值创建一个 Tween:yield * signal(2, 0.3);
可以为 signal 提供动态计算值的函数,而不是实际值。请考虑以下示例:
const radius = createSignal(1);
const area = createSignal(() => Math.PI * radius() * radius());
console.log(area()); // 3.141592653589793
radius(2);
console.log(area()); // 12.566370614359172
在这里,area
信号使用 radius
信号来计算其值。
解释
为了更好地理解信号的工作原理,让我们修改之前的示例,看看面积的确切计算时间:
const radius = createSignal(1);
const area = createSignal(() => {
console.log('area recalculated!');
return Math.PI * radius() * radius();
});
area(); // area recalculated!
area();
radius(2);
area(); // area recalculated!
radius(3);
radius(4);
area(); // area recalculated!
这演示了信号的三个重要方面:
懒惰
仅当请求信号的值时,才会计算信号。第一个
“重新计算面积!”
只有在调用 area()
后,才会将消息记录到控制台。
缓存
计算出信号后,将保存其值,然后在后续调用 area()
时返回。这就是为什么在第二次调用期间不会向控制台记录任何内容的原因。信号的这一方面使它们非常适合缓存计算量很大的操作。事实上,Motion Canvas 在内部使用信号来缓存矩阵等内容。
依赖项跟踪
区域
信号会跟踪它所依赖的其他信号。当我们更改
radius
信号,则区域
信号会收到相关通知。但它不会立即重新计算 - 懒惰仍在起作用。我们可以根据需要多次修改半径,但只有在通过调用 area()
再次请求其值时,才会重新计算面积
。
DEFAULT
值
Signals 会跟踪创建期间指定的初始值。在任何时候,
我们可以通过将 Signal 的
DEFAULT
符号添加到它:
import {DEFAULT, createSignal} from '@motion-canvas/core';
const signal = createSignal(3); // <- initial value is 3
signal(2);
signal(); // <- value is now 2
signal(DEFAULT);
signal(); // <- value is reset back to 3
yield * signal(DEFAULT, 2);
重置为默认值对于节点属性特别有用。在下面的示例中,我们设置 lineHeight
设置为 150%。
这将覆盖其默认值,该默认值将简单地从其父级继承:
const text = createRef<Txt>();
view.add(
<Txt lineHeight={'150%'} ref={text}>
Hello world!
</Txt>,
);
如果我们想重置 lineHeight
回到默认的 inherited 值,我们可以使用
默认值
:
text().lineHeight(DEFAULT);
复杂示例
我们可以利用节点的属性由信号表示这一事实来构建在数据更改时自动更新的场景。按照前面的示例,让我们为圆的面积创建一个可视化项:
您将在下面找到用于创建此动画的代码。我们突出显示了所有使用 signal 的地方:
import {Circle, Line, Txt, makeScene2D} from '@motion-canvas/2d';
import {Vector2, createSignal, waitFor} from '@motion-canvas/core';
export default makeScene2D(function* (view) {
const radius = createSignal(3);
const area = createSignal(() => Math.PI * radius() * radius());
const scale = 100;
const textStyle = {
fontWeight: 700,
fontSize: 56,
offsetY: -1,
padding: 20,
cache: true,
};
view.add(
<>
<Circle
width={() => radius() * scale * 2}
height={() => radius() * scale * 2}
fill={'#e13238'}
/>
<Line
points={[
Vector2.zero,
() => Vector2.right.scale(radius() * scale),
]}
lineDash={[20, 20]}
startArrow
endArrow
endOffset={8}
lineWidth={8}
stroke={'#242424'}
/>
<Txt
text={() => `r = ${radius().toFixed(2)}`}
x={() => (radius() * scale) / 2}
fill={'#242424'}
{...textStyle}
/>
<Txt
text={() => `A = ${area().toFixed(2)}`}
y={() => radius() * scale}
fill={'#e13238'}
{...textStyle}
/>
</>,
);
yield* radius(4, 2).to(3, 2);
yield* waitFor(1);
});
通过此设置,我们需要做的就是为 radius
信号制作动画,场景的其余部分将相应地进行调整:
yield * radius(4, 2).to(3, 2);