傳送門:從0到1,開發一個動畫庫(1)javascript
上一節講到了最基礎的內容,爲動畫構建「幀-值」對應的函數關係,完成「由幀到值」的計算過程。這一節將在上節代碼的基礎上談談如何給一個完整的動畫添加各種事件。css
在添加各種事件以前,咱們先對_loop
循環函數進行一些改進:html
_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
方法分別去處理結束、暫停和重置這三種狀態,接下來咱們依次講解這些狀態的處理。java
咱們在Core類下增長_end
、end
和renderEndState
方法,end
方法用於主動結束動畫:segmentfault
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
方法,使其直接到終止狀態。app
_end
函數的做用有三個:函數
end
(爲什麼要重複設置一次狀態呢?這不是多餘的嗎?其實,假若咱們主動觸發end
去結束動畫,這的確是多餘的,但若是是動畫本身進行到了末尾,也就是t >= d
的時刻,則必須得在_end
中去設置狀態,以確保它處於結束狀態)_renderEndState
方法,將目標變成結束狀態重置動畫的方式也是大同小異,與上面同樣oop
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); }
讓動畫暫停也是與上述二者同樣,但惟一的區別是,須要給_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); }
咱們要在動畫開始執行的時候觸發onPlay
事件,只需在_play
方法內增長一行代碼便可:this
_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>
看到這裏,咱們第二節的內容就結束啦,下一節,咱們將介紹:
下一節再見啦^_^