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


前言

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

 

 

界面

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

 

動效與交互

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

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 測試了一下:

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

  

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

  

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

backface-visibility: hidden;
perspective: 1000;

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

再回到某國產移動瀏覽器上測試,終於沒有那種死活轉不起來的感覺了,感覺跟原生應用差不多了,其實原生應用無非也是用了 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

 

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

 

(圖片出處:小周)

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


免責聲明!

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



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