天天看點

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

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

示例代碼托管在:http://www.github.com/dashnowords/blogs

部落格園位址:《大史住在大前端》原創博文目錄

華為雲社群位址:【你要的前端打怪更新指南】

目錄

  • 一. CSS動畫 和 JS動畫
    • 1.1 CSS動畫
    • 1.2 JS動畫
    • 1.3 小結
  • 二. 使用Velocity.js實作動畫

Web動畫的本質是元素狀态改變造成的樣式變更,CSS動畫和JS動畫的差別并不是由語言來決定的,而是由兩者的特點和适用場景來判斷的。

CSS

動畫簡潔高效,提升互動體驗而編寫的代碼可以輕松地和主要業務邏輯之間實作隔離,開發中建議優先使用;而當你需要更豐富的緩動函數,多對象關關聯畫或是需要在動畫執行的特定時間點關聯一些其他的業務邏輯等需要細節控制的場景中,

JS

動畫就會顯得更加清晰且易維護,兩者從來都不是非黑即白的選項。

CSS

動畫通常指使用

transition

實作的過渡動畫和使用

animation

來實作的關鍵幀動畫。

transition動畫

transition

動畫也被稱為“簡易補間動畫”,需要提供起始和結束兩個關鍵幀,浏覽器才能夠完成樣式差異比對并計算出對應的過渡動畫。開發者編寫的

CSS

代碼會在渲染之前被浏覽器使用(也就是生成

CSSOM

的過程),是以對于被渲染出來的元素而言,首屏渲染的結果就可以被當做是起始關鍵幀,那麼結束關鍵幀從哪裡來?首先通過

JS

腳本來修改指定元素的樣式或是類名是可行的,另一種方式就是利用帶有互動事件屬性的

CSS僞類

(例如

:hover

或是

:focus

),當對應的事件觸發時,新的樣式就會作用于指定元素,這種特性也可以了解為

CSS

文法中的事件回調機制。當結束關鍵幀被建立後,浏覽器就可以自動計算兩者之間的差異并完成過渡動畫。

transition

動畫的要點就是具有樣式差異的兩個關鍵幀。如果

CSS

代碼中隻包含一般的靜态選擇器(指

CSS

代碼中不包含能夠造成HTML元素狀态變更的選擇器),那麼被渲染出的元素在整個生命周期中就隻會擁有一個關鍵幀,也就是首次被渲染時的樣式,而1個關鍵幀或是2個沒有樣式差異的關鍵幀都無法進行插值計算,這也就不難了解為什麼首屏渲染時

transition

不會生效了。

是以

transition

動畫比較适合被用來實作指定元素在兩個明确的包含樣式差異的狀态之間往複切換的場景,像是滑鼠的移入移出,元素的聚焦失焦等。

animation動畫

animation

動畫需要使用

@keyframes

關鍵詞先将動畫過程抽象出來,然後将其關聯給指定元素的

animation

屬性,它可以看做是

transition

動畫的加強版。

使用

@keyframes

定義動畫時通常需要指定

from

to

兩個狀态(也可以使用

100%

),這意味着開發者隻要按照文法要求去定義一個動畫過程,它至少會包含兩個關鍵幀,是以即使沒有

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

面闆中來檢視動畫的觸發效果:

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

最後,

animation

動畫最顯著的特點就是起止狀态之間可以定義多個中間幀,這部分就不再贅述。綜上可知,

animation

是一種強制執行的動畫,既對

transition

過渡動畫失效的場景進行了補充實作,同時也增加了動畫細節的可定制性(例如循環動畫或往複動畫的實作),但它的功能擴充仍然是針對單過程動畫的。關于

animation

動畫還不熟悉的讀者可以檢視【MDN-CSS Animations】。

JS

動畫并不是指

Web Animations API

(MDN文檔——Web Animations API ),它畢竟還隻是個草案,了解一下即可。本節所說的

JS

動畫,既包括在腳本中修改元素類名或動畫樣式的方式,也包括差別于【關鍵幀動畫】的另一種形式——【逐幀動畫】。逐幀動畫不再借助浏覽器内部的插值機制來生成渲染畫面,而是将對應的邏輯在

JavaScript

中實作,每一幀的狀态都由

JS

來計算生成,然後借助

requestAnimationFrame

來将動畫中的每一幀傳遞到渲染管線中,你可以使用任何自定義的時間函數來執行動畫,也可以同時友善地管理多個對象的多個不同動畫,另外動畫的進度也是全生命周期可感覺的(

CSS

動畫隻有

animationstart

animationend

等少量的事件),你可以自由地實作動畫暫停或者恢複,又或者是在動畫執行到某一特定時刻時觸發其他的邏輯,很明顯,

JS

動畫在細節控制能力、過程管理能力以及多對象管理能力上都要比純CSS動畫更強大,但随之而來的複雜性也是必須要付出的代價,另一方面,

JS

代碼運作在主線程之中,主線程的實時工況會對動畫的流暢度造成極大影響,而

CSS

動畫則不必擔心。

以一個清單項的渲染動畫為例,通常都會采用階梯交錯動畫(也稱為

stagger動畫

)來實作,階梯交錯動畫中,每一個元素執行的動畫實際上是一樣的,但是需要在前一個元素的動畫過程執行到特定時間點時自己才能開始執行動畫,後續的元素依次類推,就需要為每一個動畫執行項的

animation

屬性設定遞增的

delay

值,這樣的需求使用原生

CSS

既難編寫也難維護,它通常需要借助預編譯器才能夠實作,但是如果在

JS

腳本中來完成相同的設定,相信大部分前端開發者都可以輕松做到。

是以綜上可知,動畫的編寫姿勢,實際上就是在

CSS

的簡潔性和

JS

的細節控制力之間找到一個平衡點。

CSS

動畫可以使用著名的

animate.css

預設動畫庫,而

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文法,還是項目中已經引用了

jQuery

Zepto

,都可以在傳回的結果集上以對象方法的形式來調用

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.js

V2

版本還處在

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

的幫助下基本都得到了解決。下一篇中将分析浏覽器高性能動畫的實作,敬請期待。