本次解析將分爲2篇文章,當前是第一篇,第二篇在這裏git
另外,爲了能更好的理解這個庫,我的寫了一個此庫的壓縮版,實現了核心的功能(主要也是爲了更好理解核心功能),內容更少方便閱讀,
地址在這裏github
anime一個JS輕量動畫庫,摒棄了常規的left,top
屬性,全面採用requestAnimateFrame+CSS3屬性
能充分調用設備進行GPU渲染。算法
它的亮點有如下(直接引用官網):segmentfault
- Keyframes(幀動畫): Chain multiple animation properties.
- Timeline(同步動畫): Synchronize multiple instances together.
- Playback controls(暫停回放功能): Play, pause, restart, seek animations or timelines.
- CSS transforms(CSS動畫): Animate CSS transforms individually.
- Function based values(函數定義配置(注入了內部屬性)): Multiple animated targets can have individual value.
- SVG Animations(SVG動畫): Motion path, line drawing and morphing animations.
- Easing functions(自定義貝塞爾函數): Use the built in functions or create your own Cubic Bézier curve easing.
這麼多亮點,其實關鍵函數就3~4個。promise
由於這裏都是使用緩動函數算法,也就是經過 初始位置
, 結束位置
, 持續時間
,已消耗的時間
計算出當前所在位置。函數
初始位置
、結束位置
和持續時間
是做爲參數傳入配置的,所以計算已消耗時間
就是完成動畫的核心。 動畫
下面就深刻了解下它的核心。ui
先了解幾個時間的變量,動畫都是算法+時間=位置
這麼算出來的:rest
// 記錄當前位置所對應的時間,根據lastTime計算 instance.cuurentTime // 記錄當前位置所消耗的時間 engineTime // 記錄上一次計算完畢賦值後的位置對應時間 lastTime // 上一次調用raf的時間 startTime // 當前位置所消耗時間(能匹配反轉狀態),根據engineTime計算 insTime // 動畫持續時間 insDuration // 延遲時間 delay // 從什麼時間點開始動畫 insOffset
接着看幾個關鍵函數,這裏先不放具體代碼,只是先知道是作什麼的(按一個正常動畫順序排放):code
// anime的核心機制, 遞歸調用raf執行(關鍵) const engine = (() => { // ...requestAnimateFrame })(); // anime主體 function anime(params){ // 定義instance 也是最終返回值 let instance = createNewInstance(params); // 外部API 從當前位置開始執行動畫 instance.play = function() {} // 配置 startTime 和 engineTime(關鍵) instance.tick = function(t) {} // 對當前engineTime進行判斷,肯定動畫方案(關鍵) function setInstanceProgress(engineTime) {} // 計算動畫當前位置 而且賦值(關鍵) function setAnimationsProgress(insTime){} // 直接跳到參數time的時間所在的位置 instance.seek = function(time) {} // 外部API 暫停 instance.pause = function() {} // 外部API 反轉 instance.reverse = function() {} // 外部API reset instance.reset = function() {} // 外部API 從新開始 instance.restart = function() {} /*...*/ return instance }
關鍵函數就4個,其餘都是一些對關鍵函數的具體使用
接着一個個解析:
其實就是對屬性和方法合併成一個總體對象,這個對象是貫穿全局的,所以裏面什麼都有...
function createNewInstance(params) { /* 對params進行處理 */ const instanceSettings = replaceObjectProps(defaultInstanceSettings, params); const tweenSettings = replaceObjectProps(defaultTweenSettings, params); const animatables = getAnimatables(params.targets); const properties = getProperties(instanceSettings, tweenSettings, params); const animations = getAnimations(animatables, properties); // mergeObjects(o1,o2)至關於 Object.assing({},o2,o1) return mergeObjects(instanceSettings, { children: [], animatables: animatables, animations: animations, duration: getInstanceTimings('duration', animations, instanceSettings, tweenSettings), delay: getInstanceTimings('delay', animations, instanceSettings, tweenSettings) }); }
此處先作了防禦,只有paused狀態下才會執行,lastTime
這裏是調取當前動畫的位置對應的時間,所以才能夠實現從任意位置開始動畫。
// 外部API 從當前位置開始執行動畫 instance.play = function() { if (!instance.paused) return; instance.paused = false; // 從0 開始 startTime = 0; // 調取當前動畫當前位置所對應的時間 lastTime = adjustTime(instance.currentTime); // 給 activeInstances 添加當前實例,說明這是一個正在運行的動畫 activeInstances.push(instance); // raf未啓動,調用engine if (!raf) engine(); }
anime的核心機制,經過遞歸調用requestAnimateFrame
,當檢測到須要執行動畫的集合activeInstances
有值,調用instance.tick。
// IIFE 以後調用engine至關於執行內部的play const engine = (() => { // step收到一個參數, function play() { raf = requestAnimationFrame(step); }; // 這裏的參數t是 raf的參數中能夠接受的一個時間戳,表示觸發調用的時間 function step(t) { // activeInstances指正在被執行的動畫集合 const activeLength = activeInstances.length; // 存在正在運行的動畫 if (activeLength) { let i = 0; while (i < activeLength) { // 調用tick執行 if (activeInstances[i]) activeInstances[i].tick(t); i++; } play(); } else { // 不存在正在運行的動畫 cancel cancelAnimationFrame(raf); raf = 0; } } return play; })();
tick的做用經過參數t
,raf
的一個時間戳概念,計算出距離上一次調用實際消耗的時間engineTime
。
例如:上一次調用時間戳是1000
,也就是1秒,中途忽然執行一個巨大的任務,等任務結束,時間戳是20000
,
那麼此次的engineTime
就是lastTime+20000-1000
,也就是計算此次動畫從上次位置再加上19秒的位置...
那麼anime對於這種狀況是怎麼處理呢?繼續看下一個setInstanceProgress
。
// 配置 startTime 和 engineTime instance.tick = function(t) { now = t; // startTime 若是首次執行 就是now,不然就是上一次tick的時間 if (!startTime) startTime = now; // lastTime 是上一次執行結束後動畫對應位置的時間戳 // engineTime 是到動畫目前爲止消耗的總時間,通常理論上講是lastTime+16.6667 const engineTime = (lastTime + now - startTime) * anime.speed; setInstanceProgress(engineTime); }
這個函數接受一個消耗的時間值,在內部對其進行適配和定義了各類狀況的動畫起始點,傳遞給setAnimationsProgress
。
例如,上面那個例子,若是消耗了19秒,就如進入這個判斷:從結束點開始動畫(考慮reverse的狀況)。
// 消耗的時間超出了持續時間 而且當前位置不在終點 或者 未設定持續時間 if ((insTime >= insDuration && insCurrentTime !== insDuration) || !insDuration){ if ((insTime >= insDuration && insCurrentTime !== insDuration) || !insDuration) { // 從結束點開始 setAnimationsProgress(insDuration); if (!insReversed) countIteration(); } }
setInstanceProgress(省略了一些配置的定義)
// 對當前engineTime進行判斷,肯定動畫方案 function setInstanceProgress(engineTime) { // 動畫持續時間 const insDuration = instance.duration; // 從什麼時間點開始動畫 const insOffset = instance.offset; // 加上延遲後的開始時間 const insStart = insOffset + instance.delay; // 記錄當前位置所對應的時間 const insCurrentTime = instance.currentTime; // 是不是反轉狀態 const insReversed = instance.reversed; // 當前位置所消耗時間(能匹配反轉狀態) // 這裏adjustTime就是若是是反轉狀態,則返回 insDuration-engineTime const insTime = adjustTime(engineTime); /* ... */ // 消耗的時間大於應該開始的時間 而且 消耗的時間在持續時間範圍內 if (insTime > insOffset && insTime < insDuration) { setAnimationsProgress(insTime); } else { // 消耗的時間小於應該開始的時間 而且 當前位置不在起點 if (insTime <= insOffset && insCurrentTime !== 0) { // 從頭開始 setAnimationsProgress(0); if (insReversed) countIteration(); } // 消耗的時間超出了持續時間 而且當前位置不在終點 或者 未設定持續時間 if ((insTime >= insDuration && insCurrentTime !== insDuration) || !insDuration) { // 從結束點開始 setAnimationsProgress(insDuration); if (!insReversed) countIteration(); } } setCallback('update'); // 消耗時間大於持續時間 而且在終點(不在終點的上面已經判斷了) if (engineTime >= insDuration) { if (instance.remaining) { startTime = now; if (instance.direction === 'alternate') toggleInstanceDirection(); // remaining爲false,remaining>0說明還須要繼續動畫 } else { // 完成動畫的執行 instance.pause(); if (!instance.completed) { instance.completed = true; setCallback('complete'); if ('Promise' in window) { resolve(); promise = makePromise(); } } } lastTime = 0; } }
關鍵函數setAnimationsProgress
和後續的操做函數都放在下一篇繼續解析。