天天看點

簡單的轉盤抽獎——CSS動畫優化

前言

前兩天去一家公司面試,被問到一些小遊戲的東西。面試官提到了刷紅包還有抽獎這些怎麼實作,當時簡單說了下思路,回來之後想想還是說的太輕描淡寫了,幹說不做就是耍流氓,是以就做了一個(Demo & 源碼)。啟動方式:手指在轉盤上滑動,轉盤轉動。這裡沒有像一般的抽獎程式一樣在背景指定抽獎結果,結果完全由你的手速決定的(老闆哭了。。。)

簡單的轉盤抽獎——CSS動畫優化
簡單的轉盤抽獎——CSS動畫優化

界面

界面很簡單,網上搜個圖檔或者直接搜個 demo 就有了,當然自适應也是必須的。這裡用了 Rem 來實作自适應,所有尺寸機關均用 rem,改變 html 節點的 font-size 即可實作全屏縮放,這裡設定的是當螢幕寬度小于420px的時候轉盤尺寸與屏寬城正比,當屏寬大于420px的時候轉盤尺寸固定。更多關于rem實作自适應的内容,可以看看這裡:  Here  。

動效與互動

網上看到的demo大多數是點選啟動的,就想着換個互動方式,用觸屏滑動的方式啟動。這裡很容易就能想到以滑屏速度轉動轉盤,轉動時給一個負加速度就可以實作減速了。這裡要注意使用者體驗,為了讓人有一個順暢的感覺,啟動的速度必須要相當的快,結尾的時候要慢慢的減速,營造抽獎的緊張氣氛。是以加速度是有一個先大後小的變化,跟CSS中的 ease-out 一樣的效果。代碼如下:

簡單的轉盤抽獎——CSS動畫優化
簡單的轉盤抽獎——CSS動畫優化
var rotate = document.getElementById('imgs');
var speed = vspeed = 0,
    x0 = y0 = t0 = x1 = y1 = t1 = null;

(function(){
    var lastTime = 0;
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x){
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
    };
    
    if(!window.requestAnimationFrame){
        window.requestAnimationFrame = function(callback, element){
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };
    };
    
    if (!window.cancelAnimationFrame){
        window.cancelAnimationFrame = function(id){
            clearTimeout(id);
        };
    };
})(); // Setup requestAnimationFrame when it is unavailable.

document.addEventListener('touchmove', function (e) {
    e.preventDefault();
});

rotate.addEventListener('touchstart', function (e) {
    if (e.touches.length == 1) {
        x0 = e.targetTouches[0].clientX;
        y0 = e.targetTouches[0].clientY;
        t0 = new Date().getTime();
    }
});

rotate.addEventListener('touchend', function (e) {
    var that = this,
        l = 0,
        angle = 0,
        timerID = null;
    x1 = e.changedTouches[0].clientX;
    y1 = e.changedTouches[0].clientY;
    t1 = new Date().getTime();
    l = Math.sqrt(Math.pow(x1-x0,2) + Math.pow(y1-y0,2));
    speed = l/(t1-t0)*20;
    if (speed < 10) return;
    vspeed = 0.5;
    
    var roll = function () {
        angle += speed;
        that.style.transform = 'rotateZ(' + angle + 'deg)';
        switch (true){
            case speed < -0.3:
                window.cancelAnimationFrame(timerID);
                return;
            case speed < 10 && vspeed > 0.1:
                speed -= vspeed;
                vspeed -= 0.03;
                break;
            default:
                speed -= vspeed;
                break;
        }
        timerID = window.requestAnimationFrame(roll);
    };
    roll();
});      

View Code

這裡動畫用的還是 h5 的 requestAnimationFrame 來實作,對于不支援 requestAnimationFrame 的浏覽器也做了相容。

程式設計還是相當簡單的,難點在于參數的調整,要調整速度控制整個轉動周期不能過長過短,而最麻煩的應該是最後減速階段的加速度調整了。最後階段太快了就沒有緊張的氣氛,太慢了又會有卡頓的感覺,尤其在國産移動浏覽器上表現得更為明顯。

性能優化

在沒性能優化前,在國産的移動浏覽器上卡頓得簡直瞎了眼,優化是必須的。

正常做法:分層,動态元素與靜态元素分離,也就是轉盤絕對定位、單獨占一個層,這樣的話動态元素的變化不會影響到靜态元素的布局,減少重繪。這個優化很基礎,寫頁面的時候就已經實施了,可見問題并不在此。另外之前一直聽說要用 CSS3d 變換的方法強制使用 GPU 渲染頁面提高性能,但是代碼上我已經寫了 'rotateZ()',應該已經開啟了硬體渲染了,不知道問題在哪,如何解決。空想也是無用,趕緊用 chrome developer 測試了一下:

簡單的轉盤抽獎——CSS動畫優化

可以看到資料其實還是相當不錯的,腳本、渲染和繪圖所占時間還比較合理的,但是問題會出在哪裡呢?當然是國産浏覽器身上了,那怎麼去優化呢?再用 chrome 的渲染檢測工具看看吧。打開方式:

簡單的轉盤抽獎——CSS動畫優化
簡單的轉盤抽獎——CSS動畫優化

見名知義,第一項可以檢測頁面的繪制重新整理(重繪),第二項顯示圖層邊界,第三項是顯示 FPS(Frame per Second,FPS,幀率),這幾項重點關注。因為這個是單頁應用,是以後面的滾動問題檢測和媒體仿真就略過了。啟動之後是這樣的:

簡單的轉盤抽獎——CSS動畫優化
簡單的轉盤抽獎——CSS動畫優化

最前面的綠色遮罩表示重繪區域,四周還有一圈褐色的表示渲染層,啟動動畫的時候可以發現在繪制動畫時事實上轉盤元素在不斷重繪,這應該是問題的關鍵。但怎麼解決呢?很多文章在介紹使用 GPU 渲染時候都會提到下面這兩句樣式:

backface-visibility: hidden;
perspective: 1000;      

試着用了這兩句之後,立馬出現了效果,旋轉的時候不再出現綠色的重繪框了:

簡單的轉盤抽獎——CSS動畫優化

再回到某國産移動浏覽器上測試,終于沒有那種死活轉不起來的感覺了,感覺跟原生應用差不多了,其實原生應用無非也是用了 GPU 渲染而已,當 web 能調用這些底層的話,性能上差别不會很大。這裡面其實後面一句 perspective: 1000; 是多餘的,設這麼大的透視距離目的是為了減弱 3D 效果,減少計算量,而轉盤轉動本來就沒 3D 效果,是以這裡是沒效果的。

主要是 backface-visibility: hidden 起作用。backface 就是元素背面,元素和醫院照的X光片一樣,正反兩面都可以看。隐藏背面的意思就是轉過來是空白的,什麼也沒有。如果不設定這個的話,浏覽器會連物體的背面也渲染出來。由于轉盤元素上面還疊加有指針元素,如果不指定隐藏背面的話,那浏覽器就将轉盤覆寫的範圍全部重繪,比如說我繞 X 或者 Y 軸旋轉,既然畫出轉盤還要畫出指針,那肯定是要重繪這一大塊頁面的。至于隐藏背面之後為何不再發生渲染層的重繪,這個可能跟浏覽器的渲染政策有關,這裡浏覽器應該會按照最優解去重繪所需的層,不變的層仍然保留,最後做合成算法。這一塊我也還不甚了解,這些隻是我個人的了解,有不當之處請務必指正!

PS: 後續發現隻要設定了 backface-visibility: hidden,根本不需要開啟 GPU,直接用 2D 旋轉也能得到非常好的效果!

鄧爺爺說得對,實踐是檢驗真理的唯一标準!

參考資料:

1)Accelerated Rendering in Chrome: http://www.html5rocks.com/en/tutorials/speed/layers/

2)App performance validation: https://developer.mozilla.org/en-US/Apps/Fundamentals/Performance/App_performance_validation

3)被解放的GPU CSS3動畫加速: http://www.cnblogs.com/sunshq/p/4878019.html

4)【Web動畫】CSS3 3D 行星運轉 && 浏覽器渲染原理: http://www.cnblogs.com/coco1s/p/5439619.html#3420358

5)Web animations on large screens: https://developer.mozilla.org/zh-CN/docs/Mozilla/Firefox_OS_for_TV/Web_animations_on_large_screen

 以上,碼字不易,随手點贊哈

簡單的轉盤抽獎——CSS動畫優化

(圖檔出處:小周)

 原創文章,轉載請注明出處!本文連結:http://www.cnblogs.com/qieguo/p/5481522.html 

繼續閱讀