HTML頁面的動畫的制作及性能



WEB頁面的動畫的制作及性能

簡介

目前WEB頁面做動畫的方式大的分兩種
1.JS間隔時間不斷修改元素屬性值,這也是CSS3出來前常用的做法,貌似也是唯一的做法。
2.CSS3出來之后支持動畫了。

大體的思路分這兩種。要是各樣功能組合細分就可以分出許多類來。本文主要介紹WEB頁面做動畫的一些方式以及性能分析。

我們的例子界面如下。
1.用好多個小方塊的移動來測試不同方式動畫的性能。
2.右邊幾個功能按鈕。其中有個阻塞按鈕,利用一個空循環阻塞主線程來測試不同效果。
3.畫面主要是許多個小方塊無規則的平移,沒有使用其他類似縮放,旋轉的動畫等。主要是為了使代碼更簡單寫也更簡單理解。

我的這個例子全在chrome上實現的,其他瀏覽器可能有問題。最好使用chrome來測試,並且有些特性的測試(阻塞)其他瀏覽器可能不支持。

示例在sina的sae上   http://1.cssanimation.sinaapp.com/, 源代碼在https://github.com/laozhbook/cssAnimation

 

程序效果示例截圖

 

這里也談談這個示例的制作過程,在制作過程中對於如何寫動畫以及對動畫性能的理解自然而然就慢慢理解了。

首先准備一下基本的代碼,一些與動畫無關的功能先准備好如下。

 1 <!DOCTYPE html>
 2 <html>
 3 <head lang="en">
 4     <meta charset="UTF-8">
 5     <title>CSS 動畫測試 -- jquery animate</title>
 6 </style>
 7 </head>
 8 <body>
 9 <div id="main">
10 </div>
11 <div id="btnCnt">
12     <button id="clear">Clear</button>
13     <button id="add10">加10</button>
14     <button id="add100">加100</button>
15     <button id="block">阻塞2秒</button>
16     <h1 id="prompt">阻塞中......</h1>
17 </div>
18 
19 <script src="js/jquery-1.11.0.js"></script>
20 <script>
21 $(document).ready(function() {
22     addNDiv(1);
23     $("#clear").click(function() {
24         $("#main div").remove();
25     });
26     $("#add10").click(function() {
27         addNDiv(10);
28     });
29     $("#add100").click(function() {
30         addNDiv(100);
31     });
32     $("#block").click(function() {
33         $("#prompt").show();
34         setTimeout(function() {
35             var begin = new Date().getTime();
36             while (new Date().getTime() - begin <= 2000) {
37             }
38             $("#prompt").hide();
39         }, 15);
40     });
41 });
42 
43 function addNDiv(n) {
44     var $container = $("#main");
45     var i = 0;
46     while (i < n) {
47         addOneDiv($container);
48         i++;
49     }
50 }
51 
52 function addOneDiv($container) {
53 }
54 
55 function animateDom() {
56 }
57 </script>
58 </body>
59 </html>

 

需要說明的是阻塞的代碼。JS是單線程並且沒有sleep,所以只能用一個長循環來模擬阻塞。
還要注意的是,因為JS的執行線程和UI渲染線程是相互阻塞的,可以理解成是一個線程。加這個測試按鈕主要為了測試下面一種寫法。
另外需要注意的是要確保$("#prompt").show();提示文字顯示出來后再模擬阻塞,所以我們加了個setTimeout 。否則的話立即執行阻塞將UI渲染也阻塞住了,提示文字可能就看不到了。

$("#prompt").show();
setTimeout(function() {
    var begin = new Date().getTime();
    while (new Date().getTime() - begin <= 2000) {
    }
    $("#prompt").hide();
}, 15);

 

下面開始動畫部分的介紹。

第一種用jquery的animate

CSS3之前做開始做動畫只能是間隔時間修改元素值,jquery的動畫就是這樣做法。因為jquery包裝好了animate方法,故此我們拿它來舉例。

這里不打算介紹這個方法的細節。具體細節還是看官方文檔來的好。我們將動畫函數功能寫成如下。
1.平移的舉例和速度(間隔)都是通過隨機傳遞過來的。讓各個小方塊的運動看起來雜亂無章。
2.jquery的animate方式支持鏈式調用讓各個動畫挨個順序執行,也支持結束后的回調函數了。
我們在最后一個動畫結束后的回調函數中做處理,再重新給自身方塊重頭執行一次動畫,我們靠這個回調來做到無限動畫下去。

function animateDom($dom, x, y, speed) {
    $dom.
    animate(
        { "left": "+=" + x + "px", "top": "+=" + y + "px" }, speed, "swing"
    ).
    animate(
        { "left": "-=" + x + "px", "top": "-=" + y + "px" },
        speed,
        "swing",
        function() {
            animateDom($(this), x, y, speed);
        }
    );
}

最后補上增加一個小方塊的函數,做的事情很簡單。
1.為了一點點視覺效果我們讓他出現的位置和背景色都隨機。
2.根據它初始的生產位置計算一下它最好往那邊平移,給他生成一個隨機平移的目標位置,防止他平移出大方框。
3.最后調用動畫函數讓其動起來。

function addOneDiv($container) {
    var mainHeight = 500,
        mainWidth = 800,
        top = Math.floor(Math.random() * mainHeight + 1),
        left = Math.floor(Math.random() * mainWidth + 1);

    var $div = $("<div></div>").addClass("animate").css({
        "top": top + "px",
        "left": left + "px",
        "background-color": '#' + Math.floor(Math.random()*16777215).toString(16)
    }).appendTo($container);

    var backX = left > (mainWidth / 2) ? -1 : 1;
    var absX = backX < 0 ? (mainWidth - left) : left;
    var tx = Math.floor(Math.random() * absX + 1) * backX;

    var backY = top > (mainHeight / 2) ? -1 : 1;
    var absY = backY < 0 ? (mainHeight - top) : top;
    var ty = Math.floor(Math.random() * absY + 1) * backY;

    animateDom($div, tx, ty, Math.floor(Math.random() * 2000 + 500));
}

第一個方式運行起來可以看到效果。200以內的情況下看起來都蠻流暢的。加上300個小方塊的時候就會出現明顯的卡頓掉幀情況。如果點阻塞按鈕會出現畫面停止。
后面會提到性能方面的問題,也會就這個方式性能方面的討論。

 

利用CSS制作動畫

第一例是用了第一種動畫方式,js控制的動畫。下面看看CSS來控制的動畫。

CSS3提供兩種方式來制作動畫,一個是過渡(transition),一個是動畫(animation)。
transition很容易理解,就是狀態A到狀態B的轉換,不是瞬間完成而是提供一個過渡效果。比如方塊從top:100移動到top:200這個變化我們可以加上過渡的控制。
而animation就是真正意義上的動畫,它和過渡是有區別的。可以理解成transition只是控制動畫的一小段。而動畫是完整控制整個動畫過程。

先用簡單點的transition開始。

與剛開始第一個例子比起來,過渡不需要我們做任何的JS代碼來控制整個過渡/動畫過程。
第一種做法animate()方法內部實現是用JS間隔時間來修改元素的屬性值。但是transition是CSS屬性,由瀏覽器內核實現中間值的變化。
transition的用法相對簡單,它可以控制某個屬性要不要過渡效果,過渡的時間間隔等,變化速率曲線等。具體細節可以看文檔。
比如說transition: top 2s; 就是給元素top屬性的變化加上了2秒的過渡效果。

我們第二個例子與第一個例子相似,只是用CSS 的 transition來控制小方塊的top left屬性的變化了。

div.animate {
    position: absolute;
    width: 10px;
    height: 10px;
    background-color: red;
    -webkit-transition: top 2s, left 2s;
    transition: top 2s, left 2s;
}

另外我們對animateDom做了點調整,因為過渡的動畫時間由CSS控制,為求簡化代碼我們固定過渡時間為2秒,於是speed參數給刪除了。
另外幾個參數也給刪除了。這種做法效果和第一個例子有點差別的。第一個例子小方塊的運行軌跡是固定的,但此例子中卻是隨機的。

function animateDom() {
    $(this).css({
        "top": Math.floor(Math.random() * 500 + 1),
        "left": Math.floor(Math.random() * 800 + 1)
    });
}

還有就是過渡是屬性變化結束后就會停止,跑完一個過渡期就結束了。
但它結束后有個transitionend回調函數。我們在這個回調中再重新設置一個新屬性,這樣就會無限動畫起來了。

對addOneDiv的修改如下。

 1 function addOneDiv($container) {
 2     var mainHeight = 500,
 3         mainWidth = 800,
 4         top = Math.floor(Math.random() * mainHeight + 1),
 5         left = Math.floor(Math.random() * mainWidth + 1);
 6 
 7     var $div = $("<div></div>").addClass("animate").css({
 8         "position": "absolute",
 9         "top": top + "px",
10         "left": left + "px",
11         "background-color": '#' + Math.floor(Math.random()*16777215).toString(16)
12     }).appendTo($container);
13 
14     $div.get(0).addEventListener("transitionend", animateDom.bind($div), true);
15     setTimeout(function() {
16         animateDom.call($div);
17     }, 500);
18 }

說明:$div.get(0).addEventListener("transitionend", animateDom.bind($div), true);這句話便是注冊回調,在它結束后重新調用動畫函數。

運行起來看效果,放上幾百個試試,你會發現第二個例子相對第一個例子性能會好一點。其他原因我們不多說,至少第二例JS主線程不需要做太多的運算了。

 

第三例transition + transform

CSS3帶來的不光是transition和animation。還有transform。注意這個transform和transition過渡是兩碼事,雖然單詞看起來很相似。
transform是CSS提供的對元素的變換,例如平移變化,縮放,旋轉等。
舉例說平移變化,可以設置元素從原來位置往上下左右平移多少像素,這個和調整top,left是差不多的效果。之所以提到它是因為它和動畫性能的關系很大。

我們第二種例子使用的是transition + top/left 來做動畫。下面我們第三個例子將使用transition + transform做動畫。
具體transform詳細用法還是建議參考文檔,這里不贅述了。

第三個例子和第二個非常相似,只是我們控制需要過渡的屬性從top/left變為transform。

相應的修改很少,一個是CSS transition過渡控制的屬性。

-webkit-transition: -webkit-transform 2s;
transition: -webkit-transform 2s;

一個是animateDom修改top/left,變成修改transform。

function animateDom() {
    var tx = Math.floor(Math.random() * 800 + 1) + "px";
    var ty = Math.floor(Math.random() * 500 + 1) + "px";

    $(this).css({
        "transform": "translate(" + tx + ", " + ty + ")"
    });
}

另外為了代碼簡單,我們addOneDiv函數將小方塊出現的位置固定在左上角了,而不是隨機。相應的代碼修改如下。

function addOneDiv($container) {

    var $div = $("<div></div>").addClass("animate").css({
        "position": "absolute",
        "top": 0 + "px",
        "left": 0 + "px",
        "background-color": '#' + Math.floor(Math.random()*16777215).toString(16)
    }).appendTo($container);

    $div.get(0).addEventListener("transitionend", animateDom.bind($div), true);
    setTimeout(function() {
        animateDom.call($div);
    }, 500);
}

運行起來看看效果。再多放幾百個小方塊試試。會發現性能好許多,至少比第一種要好許多。
另外這個例子還有很關鍵的一點。測試阻塞按鈕,你會發現主線程阻塞的時候還是有一小方塊在跑。(這個最好在chrome上試,FF貌似不行)。這個問題后面的性能討論會提到。

 

第四例translate3d

transform支持不光支持2D變化,還支持3D變化。它提供類似的translate3d這樣的API寫法,可以做到X,Y,Z三維空間上做變換。
我們可以使用3D變換的API來完成2D的變換,只需要在 Z 軸上保持不變即可。雖然效果上一樣,但是這個小小的改動理論上卻可以大幅改進動畫的性能。
這得益於如果使用了3D變換API,那么瀏覽器會主動性的選擇使用GPU來做渲染。如果只是使用2D轉換API,那么瀏覽器可能使用也可能不用GPU,這個取決於瀏覽器的實現。

我們繼續完成第四個例子,和第三個例子的差別僅僅在於使用3D 轉換的API。

function animateDom() {
    var tx = Math.floor(Math.random() * 800 + 1) + "px";
    var ty = Math.floor(Math.random() * 500 + 1) + "px";

    $(this).css({
        "transform": "translate3d(" + tx + ", " + ty + ", 0)"
    });
}

僅僅需要把translate(100, 100)這樣的屬性值,改寫成translate3d(100,100, 0)這樣的即可。

在瀏覽器上起來測試(我這邊用的是chrome),和第三例的效果相同,性能也感覺差不多。

 

第五例真正的animation

最后我們來看CSS真正的動畫animation
animation和過渡(transition)相比,它的控制力度更加精准。可以把動畫(animation)看成是多個過渡(transition)的組合。

動畫animation的API相對過渡(transition)要復雜一點。
第一要先定義一個動畫的過程,同時也給這個動畫過程一個名字。

@keyframes anim01 {
    0%   {left:0px; top:0px;}
    50%  {left:50px; top:50px;}
    100% {left:100px; top:100px;}
}

最后根據這個名字引用此動畫即可,規定這個動畫的時間長,速度曲線,是否循環等。

div {
    animation: anim01 2s;
}


我們看第五個例子代碼。使用animation + transform3d來完成。
從上面的整個transition的例子可以看出,要完成我們的測試例子,animation也可以組合left + top, transform2d, transform3d這三種搭配。
但是從寫法來說它都沒什么差別,從性能角度來說,transition相關的三個例子可以參考,沒必要再繼續細分了。


首先我們得先加入一個最基礎的動畫CSS,一個@keyframes

@-webkit-keyframes anim01 {
    0%   {-webkit-transform:translate(10px, 10px);}
    100% {-webkit-transform:translate(500px, 500px);}
}

然后讓小方塊的樣式使用此動畫。

div.animate {
    position: absolute;
    width: 10px;
    height: 10px;

    -webkit-animation-name: anim01;
    -webkit-animation-duration: 2s;
    -webkit-animation-iteration-count: infinite;
    -webkit-animation-timing-function: linear;
}

因為CSS動畫(animation)安全由CSS控制,所以我們可以將代碼中所有控制動畫的代碼都刪除掉。由CSS的一個class控制即可。
animateDom函數可以刪除掉,調用它的代碼都可以刪除掉。

這里有個稍微麻煩點的問題。我們想要的效果是各個小方塊隨機雜亂的走動,但我們此時只定義了固定的一個@keyframes,現在運行起來每個小方塊都是固定的路徑,並且點add10, add100的時候可以看到小方塊完全重疊了。
我們需要動態改變@keyframes關鍵幀。好在用JS控制@keyframes也是可行的,否則我們這個例子就做不到隨機軌跡了。

那么如何動態修改@keyframes呢,用到的是WebkitCSSKeyFramesRule這個對象,它有三個接口,deleteRule, insertRule, findRule舉例如下。

WebkitCSSKeyFramesRule.deleteRule("50%");
WebkitCSSKeyFramesRule.findRule("50%");
WebkitCSSKeyFramesRule.insertRule("50%", "{left: 0px}");
keyframesRule.insertRule("50% {left: 0px;}");

像KEY/VALUE一樣操作就可以了。具體可以去查閱詳細文檔。

代碼中我們新建一個函數,用於修改我們唯一的@keyframes

function updateAnimateRule() {
    var beginX = Math.floor(Math.random() * 800 + 1) + "px";
    var endX = Math.floor(Math.random() * 800 + 1) + "px";
    var beginY = Math.floor(Math.random() * 500 + 1) + "px";
    var endY = Math.floor(Math.random() * 500 + 1) + "px";

    var beginStr = "0% {-webkit-transform:translate3d(" + beginX + ", " + beginY + ", 0px);}";
    var endStr = "100% {-webkit-transform:translate3d(" + endX + ", " + endY + ", 0px);}";

    var keyframesRule = document.styleSheets[0].cssRules[5];
    keyframesRule.deleteRule("0%");
    keyframesRule.deleteRule("100%");
    keyframesRule.insertRule(beginStr);
    keyframesRule.insertRule(endStr);
}

每次添加完一個小方塊就更新一次@keyframes,更新函數中小方塊的路徑隨機,這樣我們的需求就差不多了OK了。

function addOneDiv($container) {

    var $div = $("<div></div>").addClass("animate").css({
        "position": "absolute",
        "top": 0 + "px",
        "left": 0 + "px",
        "background-color": '#' + Math.floor(Math.random()*16777215).toString(16)
    }).appendTo($container);

    updateAnimateRule();
}

如果你現在運行起來發現還是沒有想象中的效果,小方塊還是重疊的。
要完全結束還需要一個小技巧,我們每次添加小方塊的時候設置一個時間間隔就可以了。
addNDiv函數改成如下,用一個setInterval來排隊添加小方塊,關鍵是保證每次修改后的@keyframes都能生效。

function addNDiv(n) {
    var timer = null;
    var $container = $("#main");
    var i = 0;
    var timer = setInterval(function() {
        addOneDiv($container);
        i++;
        if (i >= n) {
            if (timer) {
                clearInterval(timer);
            }
        }
    }, 20);
}

現在運行起來就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做動畫卻可以很方便的控制。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM