因为每一个加入 Timeline 队列的 Animation 动画都可能有不一样的 delay,也就是说有不一样的开始动画的时间。所以我们需要在 Timeline 类中的 constructor 下建立一个 START_TIMES 存储空间,把我们所有 Animation 对应的开始时间都存储起来。
export class Animation { constructor(object, property, startValue, endValue, duration, delay, timingFunction) { this.object = object; this.property = property; this.startValue = startValue; this.endValue = endValue; this.duration = duration; this.timingFunction = timingFunction; this.delay = delay; } run(time) { console.log(time); let range = this.endValue - this.startValue; this.object[this.property] = this.startValue + (range * time) / this.duration; } }
然后在 Timeline 加入动画的 add 方法中,把动画的开始时间加入到 START_TIMES 数据里面。如果使用者没有给 add 方法传入 startTime 参数,那么我们需要给它一个默认值为 Date.now() 。
add(animation, startTime) { if (arguments.length < 2) startTime = Date.now(); this[ANIMATIONS].add(animation); this[START_TIMES].set(animation, startTime); }
接下来我们就可以去改造开始时间的逻辑:
第一种情况: 如果我们动画的开始时间是小于,Timeline 的开始时间的,那么我们当前动画的时间进度就是 当前时间 - Timeline 开始时间
第二种情况: 动画的开始时间大于 Timeline 的开始时间,那么当前动画的时间进度就是 当前时间 - 动画的开始时间
代码实现如下:
start() { let startTime = Date.now(); this[TICK] = () => { let now = Date.now(); for (let animation of this[ANIMATIONS]) { let t; if (this[START_TIMES].get(animation) < startTime) { t = now - startTime; } else { t = now - this[START_TIMES].get(animation); } if (t > animation.duration) { this[ANIMATIONS].delete(animation); t = animation.duration; } animation.run(t); } requestAnimationFrame(this[TICK]); }; this[TICK](); }
这样 Timline 就支持随时给它加入一个 animation 动画。为了方便我们测试这个新的功能,我们把 tl 和 animation 都挂载在 window 上。
这里我们就改动一下 main.js 中的代码:
start() { let startTime = Date.now(); this[TICK] = () => { let now = Date.now(); for (let animation of this[ANIMATIONS]) { let t; if (this[START_TIMES].get(animation) < startTime) { t = now - startTime; } else { t = now - this[START_TIMES].get(animation); } if (t > animation.duration) { this[ANIMATIONS].delete(animation); t = animation.duration; } animation.run(t); } requestAnimationFrame(this[TICK]); }; this[TICK](); }
我们重新 webpack 打包后,就可以在 console 里面执行以下命令来给 Timeline 加入一个动画:
tl.add(animation);
好,这个就是 Timeline 更新的设计。但是写到这里,我们其实还没有去让 delay 这个参数的值去让动画被延迟。
其实这里无非就在 t 的计算中,最后减去 animation.delay 即可。
if (this[START_TIMES].get(animation) < startTime) { t = now - startTime - animation.delay; } else { t = now - this[START_TIMES].get(animation) - animation.delay; }
但是我们需要注意一种特殊情况,如果我们 t - 延迟时间 得出的时间是小于 0 的话,那么代表我们的动画还没有到达需要执行的时间,只有 t > 0 才需要执行动画。所以最后在执行动画的逻辑上,加入一个判断。
if (t > 0) animation.run(t);
那么接下来我们来尝试实现它的 pause(暂停) 和 resume(恢复) 的能力。
实现暂停和重启功能首先我们来尝试加入暂停的功能。
实现 Pause要给 Timeline 实现 Pause 的能力,首先我们就要把 tick 给 cancel 掉。也就是让我们 Timline 的时间停止,如果一个钟或者手表的秒针不再动了,那么时间自然就停止了。
要取消掉 tick ,首先我们要知道触发的这个 tick 在运作的是什么。毋庸置疑,就是我们的 requestAnimationFrame。
还记得我们一开始声明的 TICK_HANDLER 吗?这个常量就是用来存储我们当前 tick 的事件的。
所以第一步就是用 TICK_HANDLER 来储存我们的 requestAnimationFrame。tick 的启动是在我们 Timeline 类中的 start 方法中启动的,所以这里我们需要改动 start 方法中的 requestAnimationFrame:
start() { let startTime = Date.now(); this[TICK] = () => { let now = Date.now(); for (let animation of this[ANIMATIONS]) { let t; if (this[START_TIMES].get(animation) < startTime) { t = now - startTime - animation.delay; } else { t = now - this[START_TIMES].get(animation) - animation.delay; } if (t > animation.duration) { this[ANIMATIONS].delete(animation); t = animation.duration; } if (t > 0) animation.run(t); } this[TICK_HANDLER] = requestAnimationFrame(this[TICK]); }; this[TICK](); }
然后我们在 pause() 方法中调用以下 cancelAnimationFrame 。
pause() { cancelAnimationFrame(this[TICK_HANDLER]); }
Pause(暂停) 还是比较简单的,但是 resume(重启)就比较复杂了。
实现 Resume那么实现 resume 的第一步必然就是重新启动 tick。但是 tick 中的 t(动画开始时间)肯定是不对的,所以我们要想办法去处理 pause 当中的逻辑。
在实现 Resume 之前,我们需要弄一点 DOM 的东西来测试它。所以我们先建立一个新的 HTML,在里面建立一个 div 元素。