传送门:从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的代码也更新以下,增添了各种按钮,主动触发目的的各种事宜:
<!DOCTYPE html>
<html>
<head>
<title></title>
<style type="text/css">
#box {
width: 100px;
height: 100px;
background: green;
}
</style>
</head>
<body>
<div id="box"></div>
<button id="start">START</button>
<button id="end">END</button>
<button id="stop">STOP</button>
<button id="reset">RESET</button>
<script type="text/javascript" src="timeline.min.js"></script>
<script type="text/javascript">
const el = (name) => document.querySelector(name);
const box = el('#box');
const timeline = new Timeline({
duration: 3000,
value: [[0, 400], [0, 600]],
render: function(value1, value2) {
box.style.transform = `translate(${ value1 }px, ${ value2 }px)`;
},
timingFunction: 'easeOut',
onPlay: () => console.log('play'),
onEnd: () => console.log('end'),
onReset: () => console.log('reset'),
onStop: () => console.log('stop')
})
el('#start').onclick = () => timeline.play();
el('#end').onclick = () => timeline.end();
el('#stop').onclick = () => timeline.stop()
el('#reset').onclick = () => timeline.reset()
</script>
</body>
</html>
看到这里,我们第二节的内容就完毕啦,下一节,我们将引见:
- 支撑自定义途径动画
- 动画间的链式挪用
下一节再会啦^_^