目前WEB頁面做動畫的方式大的分兩種
1.JS間隔時間不斷修改元素屬性值,這也是CSS3出來前常用的做法,貌似也是唯一的做法。
2.CSS3出來之後支援動畫了。
大體的思路分這兩種。要是各樣功能組合細分就可以分出許多類來。本文主要介紹WEB頁面做動畫的一些方式以及性能分析。
我們的例子界面如下。
1.用好多個小方塊的移動來測試不同方式動畫的性能。
2.右邊幾個功能按鈕。其中有個阻塞按鈕,利用一個空循環阻塞主線程來測試不同效果。
3.畫面主要是許多個小方塊無規則的平移,沒有使用其他類似縮放,旋轉的動畫等。主要是為了使代碼更簡單寫也更簡單了解。
我的這個例子全在chrome上實作的,其他浏覽器可能有問題。最好使用chrome來測試,并且有些特性的測試(阻塞)其他浏覽器可能不支援。
程式效果示例截圖

這裡也談談這個示例的制作過程,在制作過程中對于如何寫動畫以及對動畫性能的了解自然而然就慢慢了解了。
首先準備一下基本的代碼,一些與動畫無關的功能先準備好如下。
需要說明的是阻塞的代碼。JS是單線程并且沒有sleep,是以隻能用一個長循環來模拟阻塞。
還要注意的是,因為JS的執行線程和UI渲染線程是互相阻塞的,可以了解成是一個線程。加這個測試按鈕主要為了測試下面一種寫法。
另外需要注意的是要確定$("#prompt").show();提示文字顯示出來後再模拟阻塞,是以我們加了個setTimeout 。否則的話立即執行阻塞将UI渲染也阻塞住了,提示文字可能就看不到了。
下面開始動畫部分的介紹。
CSS3之前做開始做動畫隻能是間隔時間修改元素值,jquery的動畫就是這樣做法。因為jquery包裝好了animate方法,故此我們拿它來舉例。
這裡不打算介紹這個方法的細節。具體細節還是看官方文檔來的好。我們将動畫函數功能寫成如下。
1.平移的舉例和速度(間隔)都是通過随機傳遞過來的。讓各個小方塊的運動看起來雜亂無章。
2.jquery的animate方式支援鍊式調用讓各個動畫挨個順序執行,也支援結束後的回調函數了。
我們在最後一個動畫結束後的回調函數中做處理,再重新給自身方塊重頭執行一次動畫,我們靠這個回調來做到無限動畫下去。
最後補上增加一個小方塊的函數,做的事情很簡單。
1.為了一點點視覺效果我們讓他出現的位置和背景色都随機。
2.根據它初始的生産位置計算一下它最好往那邊平移,給他生成一個随機平移的目标位置,防止他平移出大方框。
3.最後調用動畫函數讓其動起來。
第一個方式運作起來可以看到效果。200以内的情況下看起來都蠻流暢的。加上300個小方塊的時候就會出現明顯的卡頓掉幀情況。如果點阻塞按鈕會出現畫面停止。
後面會提到性能方面的問題,也會就這個方式性能方面的讨論。
第一例是用了第一種動畫方式,js控制的動畫。下面看看CSS來控制的動畫。
CSS3提供兩種方式來制作動畫,一個是過渡(transition),一個是動畫(animation)。
transition很容易了解,就是狀态A到狀态B的轉換,不是瞬間完成而是提供一個過渡效果。比如方塊從top:100移動到top:200這個變化我們可以加上過渡的控制。
而animation就是真正意義上的動畫,它和過渡是有差別的。可以了解成transition隻是控制動畫的一小段。而動畫是完整控制整個動畫過程。
與剛開始第一個例子比起來,過渡不需要我們做任何的JS代碼來控制整個過渡/動畫過程。
第一種做法animate()方法内部實作是用JS間隔時間來修改元素的屬性值。但是transition是CSS屬性,由浏覽器核心實作中間值的變化。
transition的用法相對簡單,它可以控制某個屬性要不要過渡效果,過渡的時間間隔等,變化速率曲線等。具體細節可以看文檔。
比如說transition: top 2s; 就是給元素top屬性的變化加上了2秒的過渡效果。
我們第二個例子與第一個例子相似,隻是用CSS 的 transition來控制小方塊的top left屬性的變化了。
另外我們對animateDom做了點調整,因為過渡的動畫時間由CSS控制,為求簡化代碼我們固定過渡時間為2秒,于是speed參數給删除了。
另外幾個參數也給删除了。這種做法效果和第一個例子有點差别的。第一個例子小方塊的運作軌迹是固定的,但此例子中卻是随機的。
還有就是過渡是屬性變化結束後就會停止,跑完一個過渡期就結束了。
但它結束後有個transitionend回調函數。我們在這個回調中再重新設定一個新屬性,這樣就會無限動畫起來了。
對addOneDiv的修改如下。
說明:$div.get(0).addEventListener("transitionend", animateDom.bind($div), true);這句話便是注冊回調,在它結束後重新調用動畫函數。
運作起來看效果,放上幾百個試試,你會發現第二個例子相對第一個例子性能會好一點。其他原因我們不多說,至少第二例JS主線程不需要做太多的運算了。
CSS3帶來的不光是transition和animation。還有transform。注意這個transform和transition過渡是兩碼事,雖然單詞看起來很相似。
transform是CSS提供的對元素的變換,例如平移變化,縮放,旋轉等。
舉例說平移變化,可以設定元素從原來位置往上下左右平移多少像素,這個和調整top,left是差不多的效果。之是以提到它是因為它和動畫性能的關系很大。
我們第二種例子使用的是transition + top/left 來做動畫。下面我們第三個例子将使用transition + transform做動畫。
具體transform詳細用法還是建議參考文檔,這裡不贅述了。
第三個例子和第二個非常相似,隻是我們控制需要過渡的屬性從top/left變為transform。
相應的修改很少,一個是CSS transition過渡控制的屬性。
一個是animateDom修改top/left,變成修改transform。
另外為了代碼簡單,我們addOneDiv函數将小方塊出現的位置固定在左上角了,而不是随機。相應的代碼修改如下。
運作起來看看效果。再多放幾百個小方塊試試。會發現性能好許多,至少比第一種要好許多。
另外這個例子還有很關鍵的一點。測試阻塞按鈕,你會發現主線程阻塞的時候還是有一小方塊在跑。(這個最好在chrome上試,FF貌似不行)。這個問題後面的性能讨論會提到。
transform支援不光支援2D變化,還支援3D變化。它提供類似的translate3d這樣的API寫法,可以做到X,Y,Z三維空間上做變換。
我們可以使用3D變換的API來完成2D的變換,隻需要在 Z 軸上保持不變即可。雖然效果上一樣,但是這個小小的改動理論上卻可以大幅改進動畫的性能。
這得益于如果使用了3D變換API,那麼浏覽器會主動性的選擇使用GPU來做渲染。如果隻是使用2D轉換API,那麼浏覽器可能使用也可能不用GPU,這個取決于浏覽器的實作。
我們繼續完成第四個例子,和第三個例子的差别僅僅在于使用3D 轉換的API。
僅僅需要把translate(100, 100)這樣的屬性值,改寫成translate3d(100,100, 0)這樣的即可。
在浏覽器上起來測試(我這邊用的是chrome),和第三例的效果相同,性能也感覺差不多。
最後我們來看CSS真正的動畫animation
animation和過渡(transition)相比,它的控制力度更加精準。可以把動畫(animation)看成是多個過渡(transition)的組合。
動畫animation的API相對過渡(transition)要複雜一點。
第一要先定義一個動畫的過程,同時也給這個動畫過程一個名字。
最後根據這個名字引用此動畫即可,規定這個動畫的時間長,速度曲線,是否循環等。
我們看第五個例子代碼。使用animation + transform3d來完成。
從上面的整個transition的例子可以看出,要完成我們的測試例子,animation也可以組合left + top, transform2d, transform3d這三種搭配。
但是從寫法來說它都沒什麼差别,從性能角度來說,transition相關的三個例子可以參考,沒必要再繼續細分了。
首先我們得先加入一個最基礎的動畫CSS,一個@keyframes
然後讓小方塊的樣式使用此動畫。
因為CSS動畫(animation)安全由CSS控制,是以我們可以将代碼中所有控制動畫的代碼都删除掉。由CSS的一個class控制即可。
animateDom函數可以删除掉,調用它的代碼都可以删除掉。
這裡有個稍微麻煩點的問題。我們想要的效果是各個小方塊随機雜亂的走動,但我們此時隻定義了固定的一個@keyframes,現在運作起來每個小方塊都是固定的路徑,并且點add10, add100的時候可以看到小方塊完全重疊了。
我們需要動态改變@keyframes關鍵幀。好在用JS控制@keyframes也是可行的,否則我們這個例子就做不到随機軌迹了。
那麼如何動态修改@keyframes呢,用到的是WebkitCSSKeyFramesRule這個對象,它有三個接口,deleteRule, insertRule, findRule舉例如下。
像KEY/VALUE一樣操作就可以了。具體可以去查閱詳細文檔。
代碼中我們建立一個函數,用于修改我們唯一的@keyframes
每次添加完一個小方塊就更新一次@keyframes,更新函數中小方塊的路徑随機,這樣我們的需求就差不多了OK了。
如果你現在運作起來發現還是沒有想象中的效果,小方塊還是重疊的。
要完全結束還需要一個小技巧,我們每次添加小方塊的時候設定一個時間間隔就可以了。
addNDiv函數改成如下,用一個setInterval來排隊添加小方塊,關鍵是保證每次修改後的@keyframes都能生效。
現在運作起來就OK了。并且本例子和第四例不同之處在于,你點阻塞按鈕試試看。無論何時點選都不會阻塞動畫的運作。
這是因為我們動畫執行過程中完全沒有用到JS控制,一旦小方塊添加完成了JS的任務就完成了,是以點選阻塞就不會妨礙小方塊的動畫。
但是第四例的時候,我們需要用JS來控制小方塊的循環運作,是以第四例中阻塞還是可能妨礙一部分小方塊的運作的。
現在來探讨一下性能的問題。
其實制作上面例子的過程中關于性能的問題要點基本都有提到。這裡再來統一總結一下。
首先一個很重要的觀點是,我們想CSS+JS來控制動畫的性能,實際上我們能做的很有限。因為這個性能很大程度上取決于浏覽器的實作。
我們使用不同的API,不同的用法是會使性能有所不同,但能做畢竟很少。并且做種還是要取決于浏覽器如何解釋并實作你寫的代碼。
總結上面的例子,我們做動畫可以使用不同的做法,JS, animation, transition。我們可以組合屬性,transform, transform3d。
大多組合上面的例子都有執行個體驗證,以上無論用什麼組合來實作,關鍵的是一下幾點。
(JS) VS (animation 或 transition): JS占用UI主線程,而後者不用。
transform VS (屬性) : transform的渲染不在UI主線程中,也就是它不會受到JS代碼的影響(這個是比較新的版本才有的,還有其他浏覽器可能要不支援)。
另外一點,改變屬性可能會更多的引起浏覽器的重繪。例如使用transform的縮放scale(2)和改變屬性(height, width)相比,後者會引起浏覽器的重繪。
transform VS transform3d : 後者可以讓浏覽器主動性選擇GPU來渲染,也即是硬體加速。提高動畫渲染效率。
明白了以上的要點,那麼我們制作過程中選用哪種方式就心裡有數了。其實對于普通動畫要求不高的浏覽器頁面使用哪種方式都是OK的。
因為無論何種方式在PC版的浏覽器跑都不是問題,你的需求肯定不會像我們上面的例子N多小方塊一起動哪種。
但是如果是在移動終端跑的話就要注意了。我們的第一原則是盡量減少浏覽器重繪,重繪是個比較耗時步驟。第二如果能用到transform3d提高性能則更好。
至于用不用JS來主動控制(類似第一例),這個關系到不是特别重要,因為整個動畫過程中JS占的比重是很少的,例如僅僅調用JS修改一個元素的left, top是很快就可以完成的。
最終的性能關鍵還在于浏覽器的渲染過程。當然你能不用理論上性能是好一點。但是用JS做動畫卻可以很友善的控制。