sd动画框架中的交互系统

交互的困境

请看以下代码,使用一个 sd.Slider 控制矩形的 $x$ 坐标:

const svg = sd.svg();
const div = sd.div();
const slider = new sd.Slider(div).min(0).max(5).cx(270).dy(-10).value(0);
const rect = new sd.Rect(svg);
slider.onChange(value => {
    rect.startAnimate().x(value * 100).endAnimate();
});
sd.main(async () => {});

试一试,先后把 slider 滑动到 5, 2, 3 处,再按回退键,矩形会沿着 3, 2, 5, 0 的轨迹运动,最后消失。因为这本质上是顺序执行了下面的代码:

rect = new sd.Rect(svg);                  // [0, 0] 时,矩形出现
rect.startAnimate().x(500).endAnimate();  // [0, 300] 时,x: 0 -> 500
rect.startAnimate().x(200).endAnimate();  // [300, 600] 时,x: 500 -> 200
rect.startAnimate().x(300).endAnimate();  // [600, 900] 时,x: 200 -> 300

那么反过来,在回退时就是:

rect.startAnimate().x(300).endAnimate();  // [0, 300] 时,x: 300 -> 200
rect.startAnimate().x(200).endAnimate();  // [300, 600] 时,x: 200 ->500
rect.startAnimate().x(500).endAnimate();  // [600, 900] 时,x: 500 -> 0
rect = new sd.Rect(svg);                  // [900, 900] 时,矩形消失

所有的交互内产生的动画,都发生于同一动画阶段。大部分情况下我们并不希望如此,而是希望每一次交互都建立一个新的动画阶段。sd.inter (interact)为此提供了可能性。

inter

sd.inter 使用方法很简单,把交互造成的动画行为包起来就行。

const svg = sd.svg();
const div = sd.div();
const slider = new sd.Slider(div).min(0).max(5).cx(270).dy(-10).value(0);
const rect = new sd.Rect(svg);
slider.onChange(value => {
    sd.inter(async () => {
        rect.startAnimate().x(value * 100).endAnimate();
    });
});
sd.main(async () => {});

交互流程vs主流程

我们把由 sd.main 引导的流程叫做主流程。把由 sd.inter 引导的流程叫做交互流程

交互流程并不能被随意触发。想象这样一个场景。在主流程中不断为矩形赋予随机的颜色。在交互流程中,存在两次对矩形 $x$ 坐标的修改动画行为:

const svg = sd.svg();
const div = sd.div();
const C = sd.color();
const slider = new sd.Slider(div).min(0).max(5).cx(270).dy(-10).value(0);
const rect = new sd.Rect(svg);
slider.onChange(value => {
    sd.inter(async () => {
        rect.startAnimate().x(value * 100).endAnimate();
        await sd.pause();
        rect.startAnimate().x(0).endAnimate();
    });
});
sd.main(async () => {
    while (true) {
        await sd.pause();
        rect.startAnimate().color(C.random()).endAnimate();
    }
});

试一试,当交互发生时,左上角的圆点会由绿变红,预示着不能在当前交互流程未完成的情况下,重入另一个交互流程。同时主流程会被打断,直到交互动画行为完全完成后,才会继续主流程的动画行为。

有些情况下,我们希望主流程的某段执行过程不被交互流程所打断。只需要使用 sd.pause(sd.CONTINUE_FRAME) 即可。例如:

const svg = sd.svg();
const C = sd.color();
const arr = new sd.Array(svg).resize(10).forEachElement(element => {
    element.onClick(() => {
        sd.inter(async () => {
            element.startAnimate().color(C.random()).endAnimate();
        });
    });
});
sd.main(async () => {
    await sd.pause(sd.CONTINUE_FRAME); // 当执行到这一行被暂停后,交互流程不能插入进来
    arr.startAnimate().color(C.blue).endAnimate();
    await sd.pause(); // 现在交互流程可以插入进来了,可以先随意为数组元素着色,再移动数组
    arr.startAnimate().dx(40).endAnimate();
});

总体而言,交互流程在以下几种情况下不能被触发:

  • 另一个交互流程正在执行中。
  • 主流程设定了 sd.pause(sd.CONTINUE_FRAME)
  • 动画正处于历史状态上时(使用了至少一次按键 P 进行回退)。

可以观察左上角的圆点来判断当前能否触发交互流程。只有在圆点是绿色的情况下,可以触发交互流程。