實例CSS3開場動畫的制作與優化

jopen 8年前發布 | 15K 次閱讀 CSS3 WebKit CSS 前端技術

素材:

期望效果: http://v.youku.com/v_show/id_XMjY2NTc1MzYw.html

一開始需要制作這個動畫的時候,其實我是拒絕的,因為單憑這幾個素材,即便我對動畫了如指掌,也是無從下手的。后來有一天,我見到了wow軍團再臨的CG動畫,腦海中突然靈光一閃,便出現了這樣的構思:

“穿越重重的云層,伴隨著白天到黑夜的轉化,logo從遙遠的地方出現,隨后往屏幕方向快速移動,在logo快要充滿整個屏幕的時候星光一閃,又消逝在遠方…” 

于是我開始了開場動畫的坎坷歷程。

云層制作:

云層的制作耗時比較久,期間參閱了網上一些使用CSS模擬云層的博文及示例,下面列出兩個比較好的示例:

移動的云: https://codepen.io/montanaflynn/pen/orxwK

3D云: https://www.clicktorelease.com/blog/how-to-make-clouds-with-css-3d

實際上使用單圖片來模擬云層會有一些體驗問題,比如,當我們試圖穿過一片圖片生成的云時,會感覺一下就沒了,對用戶而言會產生一定的視覺落差,而真實的云層是一片區域,所以一片云我使用了5張圖片轉換方向來生成,這樣在三維的場景下我們的云就有了充實感。

下面引用一段Jaume Sanchez Elias寫的3D云的生成代碼:

function createCloud() {
    vardiv = document.createElement('div');
    div.className = 'cloudBase';
    // 隨機定位當前云團位置
    var t = 'translateX( ' + random_x + 'px )  ' +
           'translateY( ' + random_y + 'px )  ' +
           'translateZ( ' + random_z + 'px )';
    div.style.transform = t;
    world.appendChild(div);
    for (varj = 0; j < 5 + Math.round(Math.random() * 10); j++) {
        varcloud = document.createElement('div');
        cloud.className = 'cloudLayer';
        // 隨機產生云層的'translateX/translateY/translateZ/rotateZ/scale
       // CSS值,這里是云團充實感的關鍵實現
        cloud.data = {
            x: random_x,
            y: random_y,
            z: random_z,
            a: random_a,
            s: random_s
        };
        var t = 'translateX( ' + random_x + 'px )  ' + 
               'translateY( ' + random_y + 'px )  ' +
               'translateZ( ' + random_z + 'px )  ' +
               'rotateZ( ' + random_a + 'deg )  ' +
               'scale( ' + random_s + ' )';
 
        cloud.style.transform = t;
        div.appendChild(cloud);
        layers.push(cloud);
    }
    return div;
}

通過上面的方法去輸出云層,我們便擁有了質感較強的云。而為了讓動畫的加載更快一些,我將生成的云層以固定DOM的形式寫在頁面中,讓前期的JS消耗盡量減小,并且圖片的加載會在頁面加載之初進行。在有了云層DOM后,便可以開始著手穿越云層效果的制作了,穿越過程里還夾帶了從白天到黑夜的背景色變化效果,這個穿越的動作我使用translateZ屬性來實現,讓屏幕隨著時間往前推進,完成穿越云層的效果,代碼實現如下(這里僅展示webkit版本代碼):

@-webkit-keyframes angular {
    0% {
        -webkit-transform: translateZ(300px);
        opacity: 1;
    }
    100% {
        -webkit-transform: translateZ(570px);
        opacity: 1;
    }
}
 
@-webkit-keyframes dayToNight {
    0% {
        background-color: #007fd5;
        opacity: 1;
    }
    100% {
        background-color: #000;
        opacity: 1;
    }
}

星光及logo:

logo及星光的呈現過程是一個放大及放更大/縮小的過程,通過改變它的scale可以實現,實現如下:

/* 星光場景 */
@-webkit-keyframes starsense {
    0% {
        -webkit-transform: scale(1);
        opacity: 0;
    }
    30% {
        -webkit-transform: scale(25);
        opacity: 1;
    }
    90% {
        -webkit-transform: scale(0);
        opacity: 1;
    }
    100% {
        -webkit-transform: scale(0);
        opacity: 0;
    }
}
/* logo場景 */
@-webkit-keyframes logosense {
    0% {
        -webkit-transform: scale(0);
        transform: scale(0);
        opacity: 1;
    }
    20% {
        -webkit-transform: scale(1);
        transform: scale(1);
    }
    85% {
        -webkit-transform: scale(1);
        transform: scale(1);
        opacity: 1;
        -webkit-filter: blur(0);
    }
    100% {
        -webkit-transform: scale(100);
        transform: scale(100);
        opacity: 0;
        -webkit-filter: blur(50px);
    }
}

性能優化:

由于在測試中發現動畫的性能是一個比較大的問題,在一些配置比較低的機器會有很多掉幀,卡頓的現象,因此需要進行性能方面的優化。

我使用chrome里面的工具Timeline進行了動畫執行性能檢查,從中發現,在動畫執行周期內,渲染及重繪耗費的資源比較多,并且期間JS也占用了一些資源,于是我首先回頭查看了我的動畫加載函數

    function loadOpenSenseAnimation() {
        isStart = true;
        openSense.className += ' begin';
        world.className += ' begin';
        logoSense.className += ' begin';
 
        setTimeout(function() {
            viewPort.className = ' begin';
        }, 4000);
        loadingAudio === 0 && Audio.play();
        openSense.addEventListener("webkitAnimationEnd", function(){ 
            // 動畫結束時事件 
            console.log('動畫執行結束啦!'); 
            if (openSense) {
                // 執行完就抹除
                openSense.parentNode.removeChild(openSense);
            }
        }, false); 
    }

于是它變成了這樣紙:

    function loadOpenSenseAnimation() {
 
        Settings.isStart = true;
        $container.className += ' animated';
 
        $mask.addEventListener("webkitAnimationStart", function() {
            // 動畫開始時候播放
            if (Settings.isVoice && Settings.loadingAudio === 0) {
                Settings.AudioPlayer.play();
            }
        }, false);
 
        $container.addEventListener("webkitAnimationEnd", 
          function(animation) {
            // 動畫結束時事件
            if (animation.animationName === Settings.EndingAnimatedName) {
                OpenSense.pass();
            }
        }, false);
    }

這里我把setTimeout函數移除掉,使用animation-delay來接替setTimeout的位置,多個DOM操作被合并成了一個,把JS的消耗影響盡量降到最低。動畫的聲音播放使用webkitAnimationStart事件來監聽,使音樂與動畫的進行同步(這里需要注意webkitAnimationEnd/webkitAnimationStart的使用,每一個子節點的動畫開始結束都會觸發這個事件,需要判斷一下animationName 確定是否是自己需要的動畫事件),接下來我再次使用Timeline進行檢查:

可以看到,在動畫執行期間,從云層開始幀數就一直不高,結合此前的渲染重繪時間占用率過高,初步定為CSS屬性使用不當。于是我查閱了CSS動畫中所使用的屬性:

根據Timeline掉幀的時間片段,結合這個時間段內產生作用的CSS屬性,將一些可能影響性能的屬性標記了出來,并進行了排除試驗,我們發現,blur是損耗性能的主要因素,于是我對CSS做了一次排除優化,將blur,重復或不必要的屬性進行剔除。

優化結果:

多次優化后,通過Timeline得到了下面的結果,我們可以看到,除了頁面加載之初的一些掉幀,后面基本平穩在60幀,期間的無幀數是因為動畫固定在logo處停了3s左右。

在線示例: http://huangxingbang.github.io/openSense/cloud.html

總結:

1.動畫盡量使用opacity/translate/rotate /scale 這些可以讓GPU分擔工作的屬性。

2.擯棄setTimeout在動畫中的控制,動畫播放時機的控制使用animation-delay來實現,如果需要精準控制,使用RequestAnimationFrame對動畫進行更新。

3.使用webkitAnimationStart/webkitAnimationEnd對動畫并行的任務進行開始/結束控制。

參考資料:

前端性能優化(CSS動畫篇): https://segmentfault.com/a/1190000000490328

高性能 CSS3 動畫: https://www.qianduan.net/high-performance-css3-animations/

來自: http://www.alloyteam.com/2016/01/css3995527/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!