從0到1,開發一個動畫庫(2)
作者:jshao
https://segmentfault.com/a/1190000013010057
上一節講到了最基礎的內容,為動畫構建「幀-值」對應的函數關係,完成「由幀到值」的計算過程。這一節將在上節代碼的基礎上談談如何給一個完整的動畫添加各類事件。
在添加各類事件之前,我們先對_loop循環函數進行一些改進:
_loop(){
constt=Date.now()-this.beginTime,
d=this.duration,
func=Tween[this.timingFunction]||Tween["linear"];
if(this.state==="end"||t>=d){
this._end();
}elseif(this.state==="stop"){
this._stop(t);
}elseif(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(){
constd=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(){
constd=this.duration,
func=Tween[this.timingFunction]||Tween["linear"];
this._renderFunction(,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){
constd=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();
constloop=this._loop.bind(this);
window.requestAnimationFrame(loop);
}
完整代碼如下:
import Tweenfrom"./tween";
classCore{
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=>{
start:parseFloat(item[]),
end:parseFloat(item[1]),
});
})
}
_loop(){
constt=Date.now()-this.beginTime,
d=this.duration,
func=Tween[this.timingFunction]||Tween["linear"];
if(this.state==="end"||t>=d){
this._end();
}elseif(this.state==="stop"){
this._stop(t);
}elseif(this.state==="init"){
this._reset();
}else{
this._renderFunction(t,d,func)
window.requestAnimationFrame(this._loop.bind(this));
}
}
_renderFunction(t,d,func){
this.renderFunction.apply(this,values);
}
_renderEndState(){
constd=this.duration,
func=Tween[this.timingFunction]||Tween["linear"];
this._renderFunction(d,d,func);
}
_renderInitState(){
constd=this.duration,
func=Tween[this.timingFunction]||Tween["linear"];
this._renderFunction(,d,func);
}
_renderStopState(t){
constd=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();
constloop=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的代碼也更新如下,添加了各類按鈕,主動觸發目標的各類事件:
#box{
width:100px;
height:100px;
background:green;
}
START
END
STOP
RESET
constel=(name)=>document.querySelector(name);
constbox=el("#box");
consttimeline=newTimeline({
duration:3000,
value:[[,400],[,600]],
render:function(value1,value2){
},
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()
看到這裡,我們第二節的內容就結束啦,下一節,我們將介紹:
支持自定義路徑動畫
動畫間的鏈式調用
回復「面試題」「ajax」等詞,可看文章;
其它功能正在完善,不定期更新....
覺得本文對你有幫助?請分享給更多人
關注「前端大學」,提升前端技能
TAG:IT程序員 |