Web高性能動畫及渲染原理(1)CSS動畫和JS動畫

示例代碼託管在:http://www.github.com/dashnowords/blogshtml

博客園地址:《大史住在大前端》原創博文目錄前端

華爲雲社區地址:【你要的前端打怪升級指南】jquery

一. CSS動畫 和 JS動畫

Web動畫的本質是元素狀態改變形成的樣式變動,CSS動畫和JS動畫的區別並非由語言來決定的,而是由二者的特色和適用場景來判斷的。CSS動畫簡潔高效,提高交互體驗而編寫的代碼能夠輕鬆地和主要業務邏輯之間實現隔離,開發中建議優先使用;而當你須要更豐富的緩動函數,多對象關聯動畫或是須要在動畫執行的特定時間點關聯一些其餘的業務邏輯等須要細節控制的場景中,JS動畫就會顯得更加清晰且易維護,二者歷來都不是非黑即白的選項。git

1.1 CSS動畫

CSS動畫一般指使用transition實現的過渡動畫和使用animation來實現的關鍵幀動畫github

transition動畫web

transition動畫也被稱爲「簡易補間動畫」,須要提供起始和結束兩個關鍵幀,瀏覽器纔可以完成樣式差別比對並計算出對應的過渡動畫。開發者編寫的CSS代碼會在渲染以前被瀏覽器使用(也就是生成CSSOM的過程),因此對於被渲染出來的元素而言,首屏渲染的結果就能夠被當作是起始關鍵幀,那麼結束關鍵幀從哪裏來?首先經過JS腳原本修改指定元素的樣式或是類名是可行的,另外一種方式就是利用帶有交互事件屬性的CSS僞類(例如:hover或是:focus),當對應的事件觸發時,新的樣式就會做用於指定元素,這種特性也能夠理解爲CSS語法中的事件回調機制。當結束關鍵幀被建立後,瀏覽器就能夠自動計算二者之間的差別並完成過渡動畫。api

transition動畫的要點就是具備樣式差別的兩個關鍵幀。若是CSS代碼中只包含通常的靜態選擇器(指CSS代碼中不包含可以形成HTML元素狀態變動的選擇器),那麼被渲染出的元素在整個生命週期中就只會擁有一個關鍵幀,也就是首次被渲染時的樣式,而1個關鍵幀或是2個沒有樣式差別的關鍵幀都沒法進行插值計算,這也就不難理解爲何首屏渲染時transition不會生效了。瀏覽器

因此transition動畫比較適合被用來實現指定元素在兩個明確的包含樣式差別的狀態之間往復切換的場景,像是鼠標的移入移出,元素的聚焦失焦等。併發

animation動畫

animation動畫須要使用@keyframes關鍵詞先將動畫過程抽象出來,而後將其關聯給指定元素的animation屬性,它能夠看作是transition動畫的增強版。

使用@keyframes定義動畫時一般須要指定fromto兩個狀態(也可使用0100%),這意味着開發者只要按照語法要求去定義一個動畫過程,它至少會包含兩個關鍵幀,因此即便沒有CSS僞類或JS腳本的幫助,依然能夠獨立實現動畫。若是沒有定義from起始關鍵幀的樣式,animation動畫也不會出錯,它會默認以指定元素在動畫開始時刻的樣式做爲起始關鍵幀,並結合to定義的結束關鍵幀和指定元素的animation定製參數來完成補間動畫的計算,因此即便像下面這樣的簡陋寫法在首屏渲染時依然能夠生效:

<style>
    .main{
        height:100px;
        width:100px;
        animation:fadeIn 2s linear;
    }
    @keyframes fadeIn{
        to{
            background-color:yellowgreen;
        }
    }
</style>
<body>
   <div class="main"></div>
</body>

其次,和transition過渡動畫不一樣的是,animation動畫在不存在樣式差別的關鍵幀之間也會執行動畫,附件的示例demo中已經展現了上述幾種不一樣動畫實現方式,你可使用Chrome DevTools中的Animations面板中來查看動畫的觸發效果:

最後,animation動畫最顯著的特色就是起止狀態之間能夠定義多箇中間幀,這部分就再也不贅述。綜上可知,animation是一種強制執行的動畫,既對transition過渡動畫失效的場景進行了補充實現,同時也增長了動畫細節的可定製性(例如循環動畫或往復動畫的實現),但它的功能擴展仍然是針對單過程動畫的。關於animation動畫還不熟悉的讀者能夠查看【MDN-CSS Animations】

1.2 JS動畫

JS動畫並非指Web Animations API(MDN文檔——Web Animations API ),它畢竟還只是個草案,瞭解一下便可。本節所說的JS動畫,既包括在腳本中修改元素類名或動畫樣式的方式,也包括區別於【關鍵幀動畫】的另外一種形式——【逐幀動畫】。逐幀動畫再也不借助瀏覽器內部的插值機制來生成渲染畫面,而是將對應的邏輯在JavaScript中實現,每一幀的狀態都由JS來計算生成,而後藉助requestAnimationFrame來將動畫中的每一幀傳遞到渲染管線中,你可使用任何自定義的時間函數來執行動畫,也能夠同時方便地管理多個對象的多個不一樣動畫,另外動畫的進度也是全生命週期可感知的(CSS動畫只有animationstartanimationend等少許的事件),你能夠自由地實現動畫暫停或者恢復,又或者是在動畫執行到某一特定時刻時觸發其餘的邏輯,很明顯,JS動畫在細節控制能力、過程管理能力以及多對象管理能力上都要比純CSS動畫更強大,但隨之而來的複雜性也是必需要付出的代價,另外一方面,JS代碼運行在主線程之中,主線程的實時工況會對動畫的流暢度形成極大影響,而CSS動畫則沒必要擔憂。

以一個列表項的渲染動畫爲例,一般都會採用階梯交錯動畫(也稱爲stagger動畫)來實現,階梯交錯動畫中,每個元素執行的動畫其實是同樣的,可是須要在前一個元素的動畫過程執行到特定時間點時本身才能開始執行動畫,後續的元素依次類推,就須要爲每個動畫執行項的animation屬性設置遞增的delay值,這樣的需求使用原生CSS既難編寫也難維護,它一般須要藉助預編譯器纔可以實現,可是若是在JS腳本中來完成相同的設定,相信大部分前端開發者均可以輕鬆作到。

1.3 小結

因此綜上可知,動畫的編寫姿式,實際上就是在CSS的簡潔性和JS的細節控制力之間找到一個平衡點。CSS動畫可使用著名的animate.css預設動畫庫,而JS動畫能夠藉助velocity.js來實現,固然他們都不是惟一的選擇。

二. 使用Velocity.js實現動畫

velocity.js是一個很是易用的輕量級動畫庫,它包含了jQuery$.animate( )方法的所有功能,可是比jQuery更流暢。velocity.js的調用方式很是簡單,既支持全局函數的形式調用,也支持對象方法的形式調用,在源碼的主文件src/velocity.ts中能夠看到下面的代碼:

if (window) {
    const jQuery: {fn: any} = (window as any).jQuery,
        Zepto: {fn: any} = (window as any).Zepto;

    patchFn(window, true);
    patchFn(Element && Element.prototype);
    patchFn(NodeList && NodeList.prototype);
    patchFn(HTMLCollection && HTMLCollection.prototype);

    patchFn(jQuery, true);
    patchFn(jQuery && jQuery.fn);

    patchFn(Zepto, true);
    patchFn(Zepto && Zepto.fn);
}

也就是說不管你使用原生JavaScript語法,仍是項目中已經引用了jQueryZepto,均可以在返回的結果集上以對象方法的形式來調用velocity函數(固然也能夠用靜態方法的形式來調用),velocity方法具備多個方法重載,通常形式爲接收兩個參數,第一個參數是下一個關鍵幀的樣式,它和CSS中定義關鍵幀沒什麼本質區別,第二個參數是對動畫細節的定製,當屢次調用velocity對象方法時就能夠實現多步驟動畫的效果,因此在適合的場景中下面的調用都是合法的:

let element = document.querySelector('div');

//全局函數
Velocity(element, {width:200},{duration:2000});

//原生節點集合的對象方法調用
element.velocity({width:200},{duration:2000});

//jQuery或Zepto中的調用
$(element).velocity({width:200},{duration:2000});
$('div').velocity({width:200},{duration:2000});

//多步驟動畫
$('div')
    .velocity({width:200},{duration:2000})
    .velocity({height:100},{duration:2000})
    .velocity({backgroundColor:'#3498db'},{duration:2000});

velocity.jsV2版本還處在beta階段,API文檔須要在官方倉庫的wiki中查看【velocity.js V2文檔】,它提供的主要擴展能力以下:

  • 事件鉤子

    熟悉現代SPA開發的小夥伴確定不會對事件鉤子感到陌生,類組件中的生命週期鉤子就是這種形式,當用戶但願某些自定義方法能夠在特定時刻運行時,就可使用velocity中的事件鉤子將自定義方法和動畫的執行關聯起來,很明顯,這種機制的存在增長了動畫的交互和感知性,開發者能夠在各個感興趣的階段鉤入本身指望運行的函數。velocity.js中提供的事件鉤子包括:begin(在動畫開始時觸發),complete(動畫結束時觸發),progress(動畫過程當中觸發),progress鉤子每次執行時能夠獲取到動畫執行狀況的細節,例如元素的引用、完成進度的百分比、剩餘的時間以及和緩動函數有關的數據:

    element.velocity({
        width:100
    },{
        begin:function(){/*...*/},
        progress:function(elements, percentComplete, remaining, tweenValue, activeCall){},
        complete:function(){/*...*/}
    });
  • 動畫的編排和調控

    velocity.js能夠很方便地對有約束關係的多個動畫進行管理和編排。例如經過配置queue:String參數,就能夠同時維護多個隊列,以便同時管理多個併發的順序執行隊列;配置stagger:Number參數,就能夠解決上一節中提到的階梯交錯動畫的場景;speed:Number參數能夠改變更畫執行的速度;loop能夠實現往返動畫;repeat能夠實現單向重複動畫;例如前一節中說起的階梯交錯動畫就能夠用下面的代碼方便地實現:

    document.querySelectorAll('.box').velocity({marginLeft:500},{duration:5000,stagger:200});

    velocity.js中還能夠用命令的方式直接控制動畫的執行,命令的使用格式方式爲:

    element.velocity(COMMAND_STRING);

    經常使用的命令字符串包括pause(暫停動畫),resume(恢復暫停的動畫),stop(中止動畫並保持當前狀態),finish(結束動畫並應用結束狀態)以及用於註冊自定義命令、自定義緩動函數甚至自定義預設動畫等的registerXXX命令。例如一段經過按鈕點擊來控制動畫暫停和播放的代碼:

    function bindControl(){
        let flag = true;
        let dom =  document.querySelector('#btn');
        dom.addEventListener('click',function(){
            dom.velocity(flag ? 'pause':'resume');
            flag = !flag;
        });
    }
  • 集成預設動畫

    若是你曾經使用過animate.css預設動畫庫,那麼恭喜你,在velocity你依然能夠用一樣的預設動畫名來實現動畫,使用時須要引入額外的補丁庫:

    <script src="./jquery.min.js"></script>
    <script src="./velocity.min.js"></script>
    <script src="./velocity.ui.min.js"></script>

    預設動畫能夠直接傳入關鍵詞來使用:

    document.querySelector('.box').velocity('jello'); 
    //也能夠覆蓋默認的動畫參數
    document.querySelector('.box').velocity('jello',{ duration:2000 });

若是對各類動畫形式還不熟悉,能夠直接在【Animate.css官方網站】上直接查看預設動畫的效果。不難看出,純CSS動畫面臨的問題在JavaScript的幫助下基本都獲得瞭解決。下一篇中將分析瀏覽器高性能動畫的實現,敬請期待。

相關文章
相關標籤/搜索