深度剖析Lottie動畫原理

深度剖析Lottie動畫原理

導語 本文主要挖掘、弄懂lottie動畫背後的原理。lottie動畫是如何讓30FPS流暢運行?css

動畫原理

工做以來,處理過css、js、canvas、骨骼動畫,這些背後的原理都是經過把每一幀(瞬間)的靜止圖像組合起來,以必定頻率(速率)播放這些圖像造成動畫json

詳細一點解析:動畫能夠拆分紅每一幀,當前幀(靜態)圖像的屬性數據或者形態(形狀)的變動,把這樣不少幀連貫起來,就造成動畫。canvas

lottie動畫

原理其實也是這樣。但爲何它能作到如此流暢,讓衆多用戶,開發,設計師情有獨鍾。下面從幾個方面對lottie進行剖析:微信

一、如何實現一個簡單lottie動畫

用AE(Adobe After Effects)製做動畫,結合bodymovin插件把動畫導出json文件,網上不少導出案列,這裏再也不展開。而後加載lottie庫和下面幾行代碼就能夠實現一個lottie動畫。markdown

//animationData AE導出的數據源json
let params = {
  container: document.getElementById('lottie'),
  renderer: 'canvas',
  loop: true,
  autoplay: true,
  animationData: animationData
};

let anim;
anim = lottie.loadAnimation(params);
複製代碼

二、lottie動畫的數據源json

下面對json數據進行分析。左右數據一一對應。oop

fr:幀率,就是用AE作動畫的時候已經預設好,這是多少幀率(fps)的動畫。動畫

ip、op:開始、結束幀。0幀開始,180幀結束,因此這個動畫總的運行時間是6秒。由於fps是30幀/秒,因此180幀對應6秒。ui

assets:動畫所需的圖像資源信息。spa

img

layers:動畫層信息(這裏的數據就是整個lottie動畫的核心)下面詳細說明一下:插件

層級的關鍵信息是ks(層級數據信息): 以下圖所示。這5個是動畫中最基本的屬性,也是最重要的

  • a:anchor 錨點
  • o:opacity 透明度
  • p:position 位置
  • r:rotation 旋轉
  • s:scale 縮放

而每一個屬性數據中,k的值就是固定數字(靜態)或者是組合的關鍵幀數據集合(動態),而這組合的關鍵幀數據就是lottie動畫須要的數據。

img

下面舉一個列子說明這個關鍵幀數據的集合是什麼?

img

如上圖所示,其實製做的時候就是2個關鍵幀(控制scale屬性的數據),導出的數據以下。

img

(圖一)

上面的數據的翻譯:0至30幀 scale屬性值從20 變到 12,30至60幀 scale屬性值從12 變到 20,完成動畫。上面動畫之因此一直動,是lottie庫能夠設置loop循環播放。

三、lottie是如何讓30FPS流暢運行?

上面已經把json數據分析清楚,但lottie是如何去運行這些數據,而又可以讓AE導出30fps的動畫流暢渲染。

其實lottie是用了 requestAnimationFrame----在於充分利用顯示器的刷新機制,比較節省系統資源。顯示器有固定的刷新頻率(正常狀況下能達到60fps) 去渲染每一幀。這裏你可能會有疑惑,明明是30fps,爲何你要用60fps去渲染。

一開始個人思考,用60fps去渲染能保證動畫更流暢,可是問題來了。假設動畫只有30幀,製做的時候整個動畫總時間1s。可是用60fps去渲染,用0.5s就完成整個動畫播放。下面是我求知的過程:

  • 那我是否是能夠同一幀的屬性數據播放各播放2次,也變成了60幀,總時間去到1s。這個不可取,很明顯浪費了多出來的幀。
  • 那我把一幀的數據再分開一半,用2幀去完成一幀的數據屬性。(就是假設0幀 -> 1幀 scale 從0 ->0.1,如今變成0幀->1幀->2幀,scale從0->0.05->0.1) 。這樣雖然比上面好,並且能利用多出來的一幀,可是實踐告訴我,仍是有卡頓。後面我發現了是由於requestAnimationFrame渲染每一幀的間隔時間都是不固定的(上一幀的時間間隔是16ms,下一幀的間隔可能17ms),若是隻是這樣均分是不合理。

因此下面說一下lottie的處理方式:

假設json數據如圖一,整個動畫就是一共60幀,播放速度是30fps/s。2個關鍵幀 0 ->30->60。sclae屬性的變化從20->12->20

lottie的核心原理是這樣的:

  1. 使用requestAnimationFrame,而後傳nowTime當前時間。將時間換算成整個幀週期的時間currentFrame,假設當前是15.256545幀(爲了流暢,當前時間幀是帶小數點,屬性的值也是容許帶小數點
  2. 已知當前幀是15.256545,屬於第一個關鍵幀0->30動畫內。就能夠用 15.256545 / 30,這個就是 當前幀 / 時間差 * 屬性須要變化的差值8(sclae 從 20->12),因此當前幀的scale屬性值應該是 20 - (15.256545 / 30 * 8) = 15.931588。這樣就能夠確保不管設備刷新頻率是多少,是快仍是慢,都能確保當前幀下獲得的都是當前幀最準確的屬性值,直到動畫結束。

lottie動畫進階

一:速率-貝塞爾

上面只是勻速狀態下的屬性值,大多數狀況下,動畫的速率都是變化的。其實圖一還有一些數據就是用做速率的,就是圖一中的 oi 數據。其中 o 和 i 能夠是2個貝塞爾--速率控制點。而2個控制點的貝塞爾曲線屬於3階貝塞爾曲線,能夠代入三階公式

img

或者用BezierFactory(文章最後會展現)封裝好的庫,用當前時間幀佔比值(0到1),去得出屬性值佔比,從而求出以某種貝塞爾速率當前幀下的屬性值。下面是關鍵代碼

if (currentFrame >= keyData.t && currentFrame <= rotate[i+1].t) {
    dis_x = rotate[i+1].t - keyData.t;  //時間差
    dis_y = keyData.e - keyData.s;  //數值差

    if (!layer.fnc[i]) {
        layer.fnc[i] = BezierFactory.getBezierEasing(keyData.o.x, keyData.o.y, keyData.i.x, keyData.i.y, keyData.n).get;
    }

    // layer.fnc[i] 是用來求當前幀時間佔比下速率所對應的屬性值佔比
    perc = layer.fnc[i]((currentFrame - keyData.t) / dis_x);

    val = (Number(dis_y * perc) + Number(keyData.s)).toFixed(2);

    break;
}
複製代碼

二:曲線路徑-貝塞爾

經常使用的動畫中除了 opacity(透明)、scale(縮放)、rotation(旋轉)、position(位置)使用到速率的變化,而position(位置)會用到複雜的曲線路徑。曲線路徑也會使用貝塞爾,從position數據中,會發現多出2個屬性。tito,稱之爲曲線路徑控制點。固然也是一個3階的貝塞爾曲線,只不過這個是用來求路徑點軌跡,而不是求速率。

lottie動畫會利用2個控制點和貝塞爾三階公式,在一段關鍵幀動畫中,默認描繪150個路徑軌跡點來表明這一段的曲線路徑軌跡。同理,本身可使用BezFn.js庫去求出相應的150個曲線路徑點。而後結合以某種速率下屬性值佔比,而後用路徑點集合 * perc (佔比),取整求出當前幀下的當前位置的路徑點,最終實現流暢的曲線路徑動畫。下面是關鍵代碼

// layer.fnc[i] 是用來求當前幀時間佔比下速率所對應的屬性值佔比
perc = layer.fnc[i]( (currentFrame - keyData.t) / dis_x ); 
perc = perc > 1 ? 1 : perc;
index = Math.round( defaultCurveSegments * perc ) - 1 ; 
index = index < 0 ? 0 : index;

// layer.bez[i] 是利用ti、to求出的150個路徑點座標位置集合
val = layer.bez[i][index].point;
複製代碼

最後

利用lottie動畫原理,就算不用lottie庫也能實現流暢的動畫。我本身嘗試寫了一個lottie-light輕量的庫,能完成上文提到的5個屬性值變化的動畫,下面是一些實踐案例。

獅子頭部位

img

舞臺閃爍

img

但願本文對你有啓發,能夠把原理應用到各類複雜的動畫中去。但願有多一些想法交流~

更多精彩內容,盡請關注騰訊VTeam技術團隊微信公衆號和視頻號

原做者: 鍾盛照

未經贊成,禁止轉載!

相關文章
相關標籤/搜索