求索:GSAP的動畫快於jQuery嗎?/ 續 V1.1

本文是求索:GSAP的動畫快於jQuery嗎?爲什麼? 的續文。GSAP是一個js動畫插件,它聲稱「20x faster than jQuery」,是什麼讓它這麼快呢?javascript

每當有這樣的問題的時候,咱們能夠經過如下步驟來肯定一個未知的解決方案的性能優化是怎麼作到/僞造的:css

  • 黑盒:從官方用例來看,究竟有多快,快在哪兒
  • 白盒:看看官方用例以內,框架怎麼作到優化的
  • do { 提出假設,本身構建用例測試 } while (假設沒有獲得驗證);
  • 得出結論

本文是整個探究過程的後兩個部分。html

對前文的一些補充

本文可能須要你知道的一些知識前端

爲了下文閱讀方便,這裏從profiler獲得的結果角度,提一下瀏覽器裏,從定時器被激活,到用戶看到圖形的主要流程:github

CHROME
請輸入圖片描述
腳本運行(黃色)->迴流與重計算(紫色)->重繪(綠色)->其餘(白色,探查器的相關內容)ajax

IE11
請輸入圖片描述
加載(深藍色)->腳本運行(紅色)->內存垃圾回收(黃色)->迴流與重計算(綠色)->重繪(紫色)->其餘(灰色,探查器的相關內容)

注意到chrome和IE11的重繪與迴流/重計算的表示顏色是相反的。

下文中的測試,chrome的計時區間是一個gc的間隔(50s左右)(由於在chrome裏,FPS和內存佔用有比較小的反相關,內存佔用越多,FPS越低),而IE則採用10s的間隔(由於在IE 11裏,FPS和內存佔用看不出相關性)。

同時,不對不支持requestAnimationFrame的瀏覽器作出評測。

注:這裏也沒有測試firefox,由於目前尚未找到靠譜的測量UI響應的功能,若是有請@我,多謝

最後,爲了下文比較方便,咱們在chrome和IE11下再次測試官方給的用例,得到表格(單位FPS,爲平均值,後面均同樣,再也不次說明):

瀏覽器 chrome IE 11
GSAP test: jQuery 14 22
GSAP test: GSAP 45 53

假設與驗證,本身構建測試用例,看看優化是否名存實亡

根據前文所述,咱們獲得如下假設:

  • jQuery的定時器採用的是setInterval,受到瀏覽器重繪上限的控制,而GSAP採用requestAnimationFrame,徹底將重繪交給瀏覽器管理,以得到更好地重繪性能
  • jQuery每次都是單獨修改一個DOM的style,而GSAP是計算離線的style,而後再賦給DOM。這致使了不同的時間開銷。
  • jQuery沒有集中繪製,每一個DOM都在一個事件回調函數上下文中處理,有多少個DOM就有多少個上下文;GSAP有集中繪製。同時jQuery是過程化的,GSAP是面向對象的。這讓jQuery很是難以作到集中控制繪製。jQuery會將DOM的引用一路傳遞到最終改變DOM的style函數中,這在調用過程當中也會很是浪費空間。這些也都致使了不同的時間開銷。

是這樣的嗎?

測試1:setTimeout vs requestAnimationFrame

以前已經有這樣的測試了:《setInterval與requestAnimationFrame的時間間隔測試》,但有如下弊病:

  • 在那個測試沒有進行實際的任務,僅僅是空循環判斷時間而已
    ——改進:在這裏我作了一個繪製隨機div的操做,一方面儘可能接近GSAP的用例,另外一方面儘可能簡短,以減小變量對測試的影響。
  • 測試所用的時間測量很是不許確
    ——改進:測量結果,用的是chrome和IE11瀏覽器自己提供的原生性能分析工具,以取得儘可能準確的幀率。
<style>
        body{ position:relative; height:100% ; background:#000; overflow:hidden; }
        div{ width : 4px; height : 4px; border-radius : 100%; position : absolute; }
    </style>

在body內部填入N(N=500,能夠改變這個值)個div,設置它們的圓角、背景屬性。而後在每幀裏面隨機改變他們的topleft(對應着迴流)、transform(對應着重繪)值,這樣,可讓每幀的繪製裏頁面常見的繪製操做耗時均等。大體就是要繪製如下的效果。

請輸入圖片描述

測試代碼以下:

http://jsfiddle.net/humphry/HNysW/4/

CHROME結果:

setTimeout(fn,10)
請輸入圖片描述

setTimeout(fn,13)
請輸入圖片描述

setTimeout(fn,16)
請輸入圖片描述

requestAnimationFrame
請輸入圖片描述

setInterval(fn,16)
請輸入圖片描述

請輸入圖片描述
- 「我去,幀率沒有太大區別嘛。」
- 「該不是由於咱們沒有測IE嘛?」

恩,那麼咱們進入IE11,。下圖是requestAnimationFrame的測試結果,惋惜沒有統計,也不支持導出數據,不能知道平均幀率。
請輸入圖片描述

我將setInterval的結果印在requestAnimationFrame上,二者的對比可見下圖:
請輸入圖片描述

請輸入圖片描述
- 「並無沒有顯著的提高啊。」
- 「看起來必定是咱們沒有夠數據量的緣故。」

來吧,調節點的數量:

咱們如今放進去1000個小點,咱們在IE11下的結果對比如下:

請輸入圖片描述

請輸入圖片描述
- 「要說有那麼一點優化,倒是一點優化都看不出來的感受呢。」
- 「看起來必定是咱們沒有測小繪製壓力的緣故。」

或者調節成200個小點呢,咱們在IE11下幀率對比一下:

請輸入圖片描述

再綜合一下數據:

小點個數 200 500 1000
瀏覽器 chrome IE 11 chrome IE 11 chrome IE 11
requestAnimationFrame 53 60 30 32 17 19
setInterval 16 53 60 29 30 12 19

請輸入圖片描述
- 「爲何,爲何看不出來效果呀 擦 擦」
- 「阿瑪,咱們不是被涮了吧!」
- 「不可能!他們說得真真兒的,到底是什麼地方不對呢……咱們是否是把迴流和重繪弄得太均勻了啊」

測試2:setTimeout vs requestAnimationFrame對迴流的影響的測試

……好吧,那麼我構造一個這樣的文檔結構,divdivdivdiv套……讓它們都爲inline-block,以得到包裹的效果……

body{ height:100% ; background:#000; overflow:hidden; }
html{ height: 100%; }
div{ padding: 1px 3px 2px 0; border-top: 2px solid; display: inline-block; }

大概像是這樣。

請輸入圖片描述

這個時候改變最裏面的div的寬度,就會致使大面積的迴流了。

http://jsfiddle.net/humphry/SwLY7/1/

請輸入圖片描述

能夠看到,佔大比例的是迴流(紫色部分)。hover到layout處,能夠看到:

請輸入圖片描述

全員參與迴流,CPU什麼的必定很帶感呢。

結果:

參與嵌套的DIV數量 (reflow) 200 500 700 1000
瀏覽器 chrome IE 11 chrome IE 11 chrome IE 11 chrome IE 11
requestAnimationFrame 48 60 26 60 15 60 8 (崩潰)
setInterval 16 47 60 26 60 15 60 8 (崩潰)

測試3:setTimeout vs requestAnimationFrame對重繪的影響的測試

咱們來設置陰影,以期得到較長的重繪時間:

請輸入圖片描述
一閃一閃亮晶晶~

http://jsfiddle.net/humphry/XdFqR/1/

請輸入圖片描述

請輸入圖片描述

如圖,綠色的重繪佔據了大部分。

結果:

閃爍的星星數量(repaint) 80 100 200
瀏覽器 chrome IE 11 chrome IE 11 chrome IE 11
requestAnimationFrame 54 16 45 12 26 4
setInterval 16 47 16 38 10 24 5

請輸入圖片描述
- 「……」
- 「……儘管在chrome下是有一些提高,可是也沒有達到用例那麼明顯,達到兩倍的關係呀阿瑪。」

咱們來再回顧一次結果:

請輸入圖片描述

咱們前面的三個測試:

  • 隨機重排、縮放圓點測試:測試迴流+重繪
  • 嵌套inline-block大面積迴流測試:測試迴流
  • 隨機陰影模糊半徑測試:測試重繪

基本上能夠說明,在低渲染壓力/中等渲染壓力/高渲染壓力三種場景中,requestAnimationFrame裏面的重繪性能平均比setInterval快1~5幀左右,而回流性能則沒有很大影響。

那麼,GSAP是如何讓用例出現這麼大的區別呢?chrome裏快2倍,IE11裏面快1倍,這不是一個輕易換用requestAnimationFrame就能夠達到的結果。

我想到,setInterval這個函數其實並非一次觸發,而是針對每一個DOM觸發的。在此以前的全部測試,皆是集中的計時器。所以,有了第四個測試:

測試4:集中計時器 VS 分散計時器 VS 集中rAF VS 分散rAF

第四個測試,就是將前面的第一個和第三個測試改一下,將集中改爲分散。方案是,擴展HTMLDivElement原型方法,而後讓每一個DOM調用它,便可模擬在官方的jQuery測試用例中發生的事情。

HTMLDivElement.prototype.startAnimationOnMyOwn = function() {
        var that = this ;
        setInterval(function(){
            repaint(that) ;
        } , 16) ;
    } ;

    for (var i = 0; i < NUM; i++) {
        allnodes[i].startAnimationOnMyOwn() ;
    }

測試1的去中心化版:
http://jsfiddle.net/humphry/pBwBx/

測試3的去中心化版:
http://jsfiddle.net/humphry/7fa64/

獲得結果:

測試1·改(去中心化的重繪/requestAnimationFrame)

圓點數量 200 500 1000
瀏覽器 chrome IE 11 chrome IE 11 chrome IE 11
rAF (集中) 53 60 30 32 17 19
rAF (去中心化) 17 20 12 2 6 1
setInterval 16 (集中) 53 60 29 30 12 19
setInterval 16 (去中心化) 19 34 8 15 4 6

測試3·改(去中心化的重繪/requestAnimationFrame)

閃爍的陰影數量 80 100 200
瀏覽器 chrome IE 11 chrome IE 11 chrome IE 11
rAF (集中) 54 16 45 12 26 4
rAF (去中心化) 33 13 27 12 16 5
setInterval 16 (集中) 47 16 38 10 24 5
setInterval 16 (去中心化) 36 14 31 10 14 5

這個表中終於出現了很是大的數據波動,也符合GSAP的的測試結果,咱們能夠得出結論了。

得出結論

比較直觀的全部結果比較:

請輸入圖片描述

  1. setInterval是否比requestAnimationFrame更慢?
    不是的。上表中的數據能夠代表這一點,在短的回調裏,形成重繪的相關代碼,requestAnimationFrame比setInterval稍微快一些;可是在長回調函數(屢次構造requestAnimationFrame,它們會被合到一個裏面)裏,requestAnimationFrame不比setInterval快。
    這也是《理解WebKit:渲染主循環main loop和rAF》裏說:「回調函數不能太大,不能佔用太長時間,不然會影響頁面的響應和繪製的頻率」的緣由。
    requestAnimationFrame最主要的意義,是降幀而非升幀,以防止丟幀。它的目的更相似於垂直同步,而非越快越好。

    MSDN: 幀率不等或跳幀會令人感受你的站點速度緩慢。若是下降動畫速度能夠減小跳幀並有助於保持幀率一致,它可使人感受站點速度更快。
    閱讀更多:http://creativejs.com/resources/requestanimationframe/

  2. 集中定時器形成重繪是否比分散定時器形成重繪快?
    是的。這也是GSAP更快的緣由。

  3. 測試須要改進嗎?
    我認爲須要。測試過程當中爲了比較好的效果用了隨機數。其實生成隨機數的過程當中也耗費了必定的時間,更好的測試中,能夠用線性的變化替換隨機離散的數值變化,數據會更加穩定。
    同時,沒有測試firefox,很惋惜,到目前爲止,筆者依然沒有找到一款好使的分析UI的插件。

  4. GSAP的用例是否說明了GSAP快於jQuery呢?
    是的,這能夠說明GSAP更快。但並不是是在任什麼時候候都更快。在咱們須要粒子系統時快,在咱們只須要繪製一兩個小交互時,它沒有提供很是明顯的性能優化的可能性。後者能夠直接用CSS3 Animation/Translation作,或者用jQuery達到全瀏覽器兼容。若出現了粒子系統這樣的大量元素重繪的需求,用GSAP是很好的選擇。

形成GSAP更快的緣由,是因爲jQuery的處理方式,很是不適合繪製大量節點。

再次摘抄用例:

tests.jquery = {
  tween:function(dot) {
    dot[0].style.cssText = startingCSS;
    var angle = Math.random() * Math.PI * 2;
    dot.delay(Math.random() * duration).animate({left:Math.cos(angle) * radius + centerX, 
           top:Math.sin(angle) * radius + centerY, 
           width:32, 
           height:32}, duration, "cubicIn", function() { tests.jquery.tween(dot) });
  }
};
function toggleTest() {
  i = dots.length;
  while (--i > -1) {
     currentTest.tween(dots[i]);
  }
}

在正常的項目執行過程當中,咱們會使用jQuery.animate繪製大量元素嗎?有人可能會,但我不會這麼作。

GSAP相對於jQuery的進步性,就在於集中繪製了全部須要動畫更新的元素;同時也有更多的插件供選擇,能夠每過一幀改變動多的類型,而非僅僅是CSS樣式。

在項目中引入GSAP,須要引入如下三者:

<script src="http://cdnjs.cloudflare.com/ajax/libs/gsap/1.11.4/plugins/CSSPlugin.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/gsap/1.11.4/easing/EasePack.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/gsap/1.11.4/TweenLite.min.js"></script>

耗費三個鏈接,和近25Kb來加載這個組件,得到在有很大繪製任務時更快的動畫實現,這個代價值得不值得,仍是須要在具體需求具體分析了。


在最後,推薦一篇高大上全的文章:《編寫快速、高效的JavaScript代碼》。引用文中的一句話,作結語吧:

「正如咱們所見,在JavaScript的引擎世界裏面,有許多的隱藏的性能陷阱。但事實上並無性能提升的銀彈。只有當你在測試環境中結合一系列的優化,你纔會意識到最大的性能獲益。」


更新list

v1.1

前端不少測試都不乏前人,早在HTML4時代就有人測試GUI Benchmark了:GuiMark,從他給出的用例來看,他主要測試了迴流和重繪同時存在的情形。也能夠參考一下測試方式。


請輸入圖片描述
相關文章
相關標籤/搜索