摘要:初始位置結(jié)束位置和持續(xù)時間是作為參數(shù)傳入配置的,因此計算已消耗時間就是完成動畫的核心。下面就深入了解下它的核心。
本次解析將分為2篇文章,當前是第一篇,第二篇在這里
另外,為了能更好的理解這個庫,個人寫了一個此庫的壓縮版,實現(xiàn)了核心的功能(主要也是為了更好理解核心功能),內(nèi)容更少方便閱讀,
地址在這里
anime一個JS輕量動畫庫,摒棄了常規(guī)的left,top屬性,全面采用requestAnimateFrame+CSS3屬性能充分調(diào)用設備進行GPU渲染。
它的亮點有以下(直接引用官網(wǎng)):
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(函數(shù)定義配置(注入了內(nèi)部屬性)): Multiple animated targets can have individual value.
SVG Animations(SVG動畫): Motion path, line drawing and morphing animations.
Easing functions(自定義貝塞爾函數(shù)): Use the built in functions or create your own Cubic Bézier curve easing.
這么多亮點,其實關鍵函數(shù)就3~4個。
因為這里都是使用緩動函數(shù)算法,也就是通過 初始位置, 結(jié)束位置, 持續(xù)時間,已消耗的時間 計算出當前所在位置。
初始位置、結(jié)束位置和持續(xù)時間是作為參數(shù)傳入配置的,因此計算已消耗時間就是完成動畫的核心。
下面就深入了解下它的核心。
深入理解先了解幾個時間的變量,動畫都是算法+時間=位置這么算出來的:
// 記錄當前位置所對應的時間,根據(jù)lastTime計算 instance.cuurentTime // 記錄當前位置所消耗的時間 engineTime // 記錄上一次計算完畢賦值后的位置對應時間 lastTime // 上一次調(diào)用raf的時間 startTime // 當前位置所消耗時間(能匹配反轉(zhuǎn)狀態(tài)),根據(jù)engineTime計算 insTime // 動畫持續(xù)時間 insDuration // 延遲時間 delay // 從什么時間點開始動畫 insOffset
接著看幾個關鍵函數(shù),這里先不放具體代碼,只是先知道是做什么的(按一個正常動畫順序排放):
// anime的核心機制, 遞歸調(diào)用raf執(zhí)行(關鍵) const engine = (() => { // ...requestAnimateFrame })(); // anime主體 function anime(params){ // 定義instance 也是最終返回值 let instance = createNewInstance(params); // 外部API 從當前位置開始執(zhí)行動畫 instance.play = function() {} // 配置 startTime 和 engineTime(關鍵) instance.tick = function(t) {} // 對當前engineTime進行判斷,確定動畫方案(關鍵) function setInstanceProgress(engineTime) {} // 計算動畫當前位置 并且賦值(關鍵) function setAnimationsProgress(insTime){} // 直接跳到參數(shù)time的時間所在的位置 instance.seek = function(time) {} // 外部API 暫停 instance.pause = function() {} // 外部API 反轉(zhuǎn) instance.reverse = function() {} // 外部API reset instance.reset = function() {} // 外部API 重新開始 instance.restart = function() {} /*...*/ return instance }
關鍵函數(shù)就4個,其他都是一些對關鍵函數(shù)的具體使用
接著一個個解析:
createNewInstance
其實就是對屬性和方法合并成一個整體對象,這個對象是貫穿全局的,因此里面什么都有...
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) }); }
instance.play
此處先做了防護,只有paused狀態(tài)下才會執(zhí)行,lastTime這里是調(diào)取當前動畫的位置對應的時間,因此才可以實現(xiàn)從任意位置開始動畫。
// 外部API 從當前位置開始執(zhí)行動畫 instance.play = function() { if (!instance.paused) return; instance.paused = false; // 從0 開始 startTime = 0; // 調(diào)取當前動畫當前位置所對應的時間 lastTime = adjustTime(instance.currentTime); // 給 activeInstances 添加當前實例,說明這是一個正在運行的動畫 activeInstances.push(instance); // raf未啟動,調(diào)用engine if (!raf) engine(); }
engine
anime的核心機制,通過遞歸調(diào)用requestAnimateFrame,當檢測到需要執(zhí)行動畫的集合activeInstances有值,調(diào)用instance.tick。
// IIFE 之后調(diào)用engine相當于執(zhí)行內(nèi)部的play const engine = (() => { // step收到一個參數(shù), function play() { raf = requestAnimationFrame(step); }; // 這里的參數(shù)t是 raf的參數(shù)中可以接受的一個時間戳,表示觸發(fā)調(diào)用的時間 function step(t) { // activeInstances指正在被執(zhí)行的動畫集合 const activeLength = activeInstances.length; // 存在正在運行的動畫 if (activeLength) { let i = 0; while (i < activeLength) { // 調(diào)用tick執(zhí)行 if (activeInstances[i]) activeInstances[i].tick(t); i++; } play(); } else { // 不存在正在運行的動畫 cancel cancelAnimationFrame(raf); raf = 0; } } return play; })();
instance.tick
tick的作用通過參數(shù)t,raf的一個時間戳概念,計算出距離上一次調(diào)用實際消耗的時間engineTime。
例如:上一次調(diào)用時間戳是1000,也就是1秒,中途突然執(zhí)行一個巨大的任務,等任務結(jié)束,時間戳是20000,
那么這次的engineTime就是lastTime+20000-1000,也就是計算這次動畫從上次位置再加上19秒的位置...
那么anime對于這種情況是怎么處理呢?繼續(xù)看下一個setInstanceProgress。
// 配置 startTime 和 engineTime instance.tick = function(t) { now = t; // startTime 如果首次執(zhí)行 就是now,否則就是上一次tick的時間 if (!startTime) startTime = now; // lastTime 是上一次執(zhí)行結(jié)束后動畫對應位置的時間戳 // engineTime 是到動畫目前為止消耗的總時間,一般理論上講是lastTime+16.6667 const engineTime = (lastTime + now - startTime) * anime.speed; setInstanceProgress(engineTime); }
setInstanceProgress
這個函數(shù)接受一個消耗的時間值,在內(nèi)部對其進行適配和定義了各種情況的動畫起始點,傳遞給setAnimationsProgress。
例如,上面那個例子,如果消耗了19秒,就如進入這個判斷:從結(jié)束點開始動畫(考慮reverse的情況)。
// 消耗的時間超出了持續(xù)時間 并且當前位置不在終點 或者 未設定持續(xù)時間 if ((insTime >= insDuration && insCurrentTime !== insDuration) || !insDuration){ if ((insTime >= insDuration && insCurrentTime !== insDuration) || !insDuration) { // 從結(jié)束點開始 setAnimationsProgress(insDuration); if (!insReversed) countIteration(); } }
setInstanceProgress(省略了一些配置的定義)
// 對當前engineTime進行判斷,確定動畫方案 function setInstanceProgress(engineTime) { // 動畫持續(xù)時間 const insDuration = instance.duration; // 從什么時間點開始動畫 const insOffset = instance.offset; // 加上延遲后的開始時間 const insStart = insOffset + instance.delay; // 記錄當前位置所對應的時間 const insCurrentTime = instance.currentTime; // 是否是反轉(zhuǎn)狀態(tài) const insReversed = instance.reversed; // 當前位置所消耗時間(能匹配反轉(zhuǎn)狀態(tài)) // 這里adjustTime就是如果是反轉(zhuǎn)狀態(tài),則返回 insDuration-engineTime const insTime = adjustTime(engineTime); /* ... */ // 消耗的時間大于應該開始的時間 并且 消耗的時間在持續(xù)時間范圍內(nèi) if (insTime > insOffset && insTime < insDuration) { setAnimationsProgress(insTime); } else { // 消耗的時間小于應該開始的時間 并且 當前位置不在起點 if (insTime <= insOffset && insCurrentTime !== 0) { // 從頭開始 setAnimationsProgress(0); if (insReversed) countIteration(); } // 消耗的時間超出了持續(xù)時間 并且當前位置不在終點 或者 未設定持續(xù)時間 if ((insTime >= insDuration && insCurrentTime !== insDuration) || !insDuration) { // 從結(jié)束點開始 setAnimationsProgress(insDuration); if (!insReversed) countIteration(); } } setCallback("update"); // 消耗時間大于持續(xù)時間 并且在終點(不在終點的上面已經(jīng)判斷了) if (engineTime >= insDuration) { if (instance.remaining) { startTime = now; if (instance.direction === "alternate") toggleInstanceDirection(); // remaining為false,remaining>0說明還需要繼續(xù)動畫 } else { // 完成動畫的執(zhí)行 instance.pause(); if (!instance.completed) { instance.completed = true; setCallback("complete"); if ("Promise" in window) { resolve(); promise = makePromise(); } } } lastTime = 0; } }
關鍵函數(shù)setAnimationsProgress和后續(xù)的操作函數(shù)都放在下一篇繼續(xù)解析。
文章版權歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/97746.html
摘要:使用了緩動函數(shù),只需要通過當前動畫消耗的時間,搭配其他定義的配置項,就可以計算出當前動畫具體位置。此次解析就到這里結(jié)束,如有錯誤,請指出,感謝 本次解析將分為2篇文章,當前是第二篇,第一篇在這里 另外,為了能更好的理解這個庫,個人寫了一個此庫的壓縮版,實現(xiàn)了核心的功能(主要也是為了更好理解核心功能),內(nèi)容更少方便閱讀,地址在這里 繼續(xù)上一篇,先把結(jié)構(gòu)圖拉過來: // anime主體 ...
摘要:超過的,是一個動畫庫,可以處理屬性,單個轉(zhuǎn)換,或任何屬性以及對象。在,是一個快速的動畫引擎,具有與的相同的。在,這個功能和反應動畫庫只重。由和其他人使用,這個庫既流行又令人驚訝地有用。 在瀏覽網(wǎng)頁尋找一個整潔的Javascript動畫庫時,我發(fā)現(xiàn)很多recommended的動畫庫一段時間都沒有維護。 經(jīng)過一些研究,我收集了11個最好的庫,在你的應用程序中使用。我還添加了一些,主要是非維...
摘要:超過的,是一個動畫庫,可以處理屬性,單個轉(zhuǎn)換,或任何屬性以及對象。在,是一個快速的動畫引擎,具有與的相同的。在,這個功能和反應動畫庫只重。由和其他人使用,這個庫既流行又令人驚訝地有用。 在瀏覽網(wǎng)頁尋找一個整潔的Javascript動畫庫時,我發(fā)現(xiàn)很多recommended的動畫庫一段時間都沒有維護。 經(jīng)過一些研究,我收集了11個最好的庫,在你的應用程序中使用。我還添加了一些,主要是非維...
摘要:超過的,是一個動畫庫,可以處理屬性,單個轉(zhuǎn)換,或任何屬性以及對象。在,是一個快速的動畫引擎,具有與的相同的。在,這個功能和反應動畫庫只重。由和其他人使用,這個庫既流行又令人驚訝地有用。 在瀏覽網(wǎng)頁尋找一個整潔的Javascript動畫庫時,我發(fā)現(xiàn)很多recommended的動畫庫一段時間都沒有維護。 經(jīng)過一些研究,我收集了11個最好的庫,在你的應用程序中使用。我還添加了一些,主要是非維...
閱讀 3492·2023-04-26 02:44
閱讀 1629·2021-11-25 09:43
閱讀 1521·2021-11-08 13:27
閱讀 1885·2021-09-09 09:33
閱讀 903·2019-08-30 15:53
閱讀 1765·2019-08-30 15:53
閱讀 2778·2019-08-30 15:53
閱讀 3110·2019-08-30 15:44