摘要:传送门从到,开发一个动画库上一节讲到了最基础的内容,为动画构建帧值对应的函数关系,完成由帧到值的计算过程。这一节将在上节代码的基础上谈谈如何给一个完整的动画添加各类事件。
传送门:从0到1,开发一个动画库(1)
上一节讲到了最基础的内容,为动画构建“帧-值”对应的函数关系,完成“由帧到值”的计算过程。这一节将在上节代码的基础上谈谈如何给一个完整的动画添加各类事件。
在添加各类事件之前,我们先对_loop循环函数进行一些改进:
_loop() { const t = Date.now() - this.beginTime, d = this.duration, func = Tween[this.timingFunction] || Tween["linear"]; if (this.state === "end" || t >= d) { this._end(); } else if (this.state === "stop") { this._stop(t); } else if (this.state === "init") { this._reset(); } else { this._renderFunction(t, d, func) window.requestAnimationFrame(this._loop.bind(this)); } }
可以清晰地看到,我们在循环中增加了很多类型的判断,根据state当前不同的状态执行相应的处理函数:我们新增了_end、_stop、_reset方法分别去处理结束、暂停和重置这三种状态,接下来我们依次讲解这些状态的处理。
End我们在Core类下增加_end、end和renderEndState方法,end方法用于主动结束动画:
end() { this.state === "play" ? (this.state = "end") : this._end(); } _end() { this.state = "end"; this._renderEndState(); this.onEnd && this.onEnd(); } _renderEndState() { const d = this.duration, func = Tween[this.timingFunction] || Tween["linear"]; this._renderFunction(d, d, func); }
通过执行end方法,我们可以主动结束动画:如果当前目标处于运动状态,则将其设置为end,因此下一个_loop函数被执行的时候,程序就被流向了_end处理函数;若为其他状态,意味着循环没有被打开,我们就直接调用_end方法,使其直接到终止状态。
_end函数的作用有三个:
将当前状态设置为end(为何要重复设置一次状态呢?这不是多余的吗?其实,倘若我们主动触发end去结束动画,这的确是多余的,但如果是动画自己进行到了末尾,也就是t >= d的时刻,则必须得在_end中去设置状态,以确保它处于结束状态)
通过_renderEndState方法,将目标变成结束状态
若有回调函数则执行回调
Reset重置动画的方式也是大同小异,与上面一样
reset() { this.state === "play" ? (this.state = "init") : this._reset(); } _reset() { this.state = "init"; this._renderInitState(); this.onReset && this.onReset(); } _renderInitState() { const d = this.duration, func = Tween[this.timingFunction] || Tween["linear"]; this._renderFunction(0, d, func); }Stop
让动画暂停也是与上述两者一样,但唯一的区别是,需要给_renderStopState方法传入当前时间进度:
stop() { if (this.state === "play") { this.state = "stop"; } else { // 使目标暂停,无需像end或reset那样将目标变成结束/起始状态,保持当前状态即可 this.state = "stop"; this.onStop && this.onStop(); } } _stop(t) { this.state = "stop"; this._renderStopState(t); this.onStop && this.onStop(); } _renderStopState(t) { const d = this.duration, func = Tween[this.timingFunction] || Tween["linear"]; this._renderFunction(t, d, func); }play
我们要在动画开始执行的时候触发onPlay事件,只需在_play方法内增加一行代码即可:
_play() { this.state = "play"; // 新增部分 this.onPlay && this.onPlay(); this.beginTime = Date.now(); const loop = this._loop.bind(this); window.requestAnimationFrame(loop); }```
完整代码如下:
import Tween from "./tween"; class Core { constructor(opt) { this._init(opt); this.state = "init"; } _init(opt) { this._initValue(opt.value); this.duration = opt.duration || 1000; this.timingFunction = opt.timingFunction || "linear"; this.renderFunction = opt.render || this._defaultFunc; /* Events */ this.onPlay = opt.onPlay; this.onEnd = opt.onEnd; this.onStop = opt.onStop; this.onReset = opt.onReset; } _initValue(value) { this.value = []; value.forEach(item => { this.value.push({ start: parseFloat(item[0]), end: parseFloat(item[1]), }); }) } _loop() { const t = Date.now() - this.beginTime, d = this.duration, func = Tween[this.timingFunction] || Tween["linear"]; if (this.state === "end" || t >= d) { this._end(); } else if (this.state === "stop") { this._stop(t); } else if (this.state === "init") { this._reset(); } else { this._renderFunction(t, d, func) window.requestAnimationFrame(this._loop.bind(this)); } } _renderFunction(t, d, func) { const values = this.value.map(value => func(t, value.start, value.end - value.start, d)); this.renderFunction.apply(this, values); } _renderEndState() { const d = this.duration, func = Tween[this.timingFunction] || Tween["linear"]; this._renderFunction(d, d, func); } _renderInitState() { const d = this.duration, func = Tween[this.timingFunction] || Tween["linear"]; this._renderFunction(0, d, func); } _renderStopState(t) { const d = this.duration, func = Tween[this.timingFunction] || Tween["linear"]; this._renderFunction(t, d, func); } _stop(t) { this.state = "stop"; this._renderStopState(t); this.onStop && this.onStop(); } _play() { this.state = "play"; this.onPlay && this.onPlay(); this.beginTime = Date.now(); const loop = this._loop.bind(this); window.requestAnimationFrame(loop); } _end() { this.state = "end"; this._renderEndState(); this.onEnd && this.onEnd.call(this); } _reset() { this.state = "init"; this._renderInitState(); this.onReset && this.onReset(); } play() { this._play(); } end() { this.state === "play" ? (this.state = "end") : this._end(); } reset() { this.state === "play" ? (this.state = "init") : this._reset(); } stop() { if (this.state === "play") { this.state = "stop"; } else { this.state = "stop"; this.onStop && this.onStop(); } } } window.Timeline = Core;
相应地,html的代码也更新如下,添加了各类按钮,主动触发目标的各类事件:
看到这里,我们第二节的内容就结束啦,下一节,我们将介绍:
支持自定义路径动画
动画间的链式调用
下一节再见啦^_^
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/92829.html
摘要:传送门从到,开发一个动画库如今市面上关于动画的开源库多得数不胜数,有关于甚至是渲染的,百花齐放,效果炫酷。当你看到的时候可能不大明白外界传入的到底是啥其实是一个数组,它的每一个元素都保存着独立动画的起始与结束两种状态。 传送门:从0到1,开发一个动画库(2) 如今市面上关于动画的开源库多得数不胜数,有关于CSS、js甚至是canvas渲染的,百花齐放,效果炫酷。但你是否曾想过,自己亲手...
摘要:传送门从到,开发一个动画库如今市面上关于动画的开源库多得数不胜数,有关于甚至是渲染的,百花齐放,效果炫酷。当你看到的时候可能不大明白外界传入的到底是啥其实是一个数组,它的每一个元素都保存着独立动画的起始与结束两种状态。 传送门:从0到1,开发一个动画库(2) 如今市面上关于动画的开源库多得数不胜数,有关于CSS、js甚至是canvas渲染的,百花齐放,效果炫酷。但你是否曾想过,自己亲手...
摘要:幸运的是,供应似乎与需求相匹配,并且有多种选择。让我们来看看年值得关注的十大动画库。八年了,仍然是一个强大的动画工具。接下来在这个令人惊叹的动画库列表上的就是了。,通常被称为动画平台,我们忽略它在列表中的排名,它是列表中最受欢迎的库之一。 原文链接原译文链接 现代网站的客户端依靠高质量的动画,这就使得JavaScript动画库的需求不断增加。幸运的是,供应似乎与需求相匹配,并且有多种选...
阅读 703·2021-11-18 10:02
阅读 3579·2021-09-02 10:21
阅读 1725·2021-08-27 16:16
阅读 2057·2019-08-30 15:56
阅读 2385·2019-08-29 16:53
阅读 1372·2019-08-29 11:18
阅读 2951·2019-08-26 10:33
阅读 2640·2019-08-23 18:34