Home  >  Article  >  Web Front-end  >  简单的转盘抽奖CSS动画优化 - 茄果

简单的转盘抽奖CSS动画优化 - 茄果

WBOY
WBOYOriginal
2016-05-20 13:48:521740browse

前言

前两天去一家公司面试,被问到一些小游戏的东西。面试官提到了刷红包还有抽奖这些怎么实现,当时简单说了下思路,回来之后想想还是说的太轻描淡写了,干说不做就是耍流氓,所以就做了一个(Demo & 源码)。启动方式:手指在转盘上滑动,转盘转动。这里没有像一般的抽奖程序一样在后台指定抽奖结果,结果完全由你的手速决定的(老板哭了。。。)

 

 

界面

界面很简单,网上搜个图片或者直接搜个 demo 就有了,当然自适应也是必须的。这里用了 Rem 来实现自适应,所有尺寸单位均用 rem,改变 html 节点的 font-size 即可实现全屏缩放,这里设置的是当屏幕宽度小于420px的时候转盘尺寸与屏宽城正比,当屏宽大于420px的时候转盘尺寸固定。更多关于rem实现自适应的内容,可以看看这里:  Here  

 

动效与交互

网上看到的demo大多数是点击启动的,就想着换个交互方式,用触屏滑动的方式启动。这里很容易就能想到以滑屏速度转动转盘,转动时给一个负加速度就可以实现减速了。这里要注意用户体验,为了让人有一个顺畅的感觉,启动的速度必须要相当的快,结尾的时候要慢慢的减速,营造抽奖的紧张气氛。所以加速度是有一个先大后小的变化,跟CSS中的 ease-out 一样的效果。代码如下:

<span style="color: #0000ff;">var</span> rotate = document.getElementById('imgs'<span style="color: #000000;">);
</span><span style="color: #0000ff;">var</span> speed = vspeed = 0<span style="color: #000000;">,
    x0 </span>= y0 = t0 = x1 = y1 = t1 = <span style="color: #0000ff;">null</span><span style="color: #000000;">;

(</span><span style="color: #0000ff;">function</span><span style="color: #000000;">(){
    </span><span style="color: #0000ff;">var</span> lastTime = 0<span style="color: #000000;">;
    </span><span style="color: #0000ff;">var</span> vendors = ['ms', 'moz', 'webkit', 'o'<span style="color: #000000;">];
    </span><span style="color: #0000ff;">for</span>(<span style="color: #0000ff;">var</span> x = 0; x x){
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'<span style="color: #000000;">];
        window.cancelAnimationFrame </span>= window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'<span style="color: #000000;">];
    };
    
    </span><span style="color: #0000ff;">if</span>(!<span style="color: #000000;">window.requestAnimationFrame){
        window.requestAnimationFrame </span>= <span style="color: #0000ff;">function</span><span style="color: #000000;">(callback, element){
            </span><span style="color: #0000ff;">var</span> currTime = <span style="color: #0000ff;">new</span><span style="color: #000000;"> Date().getTime();
            </span><span style="color: #0000ff;">var</span> timeToCall = Math.max(0, 16 - (currTime -<span style="color: #000000;"> lastTime));
            </span><span style="color: #0000ff;">var</span> id = window.setTimeout(<span style="color: #0000ff;">function</span>() { callback(currTime +<span style="color: #000000;"> timeToCall); }, timeToCall);
            lastTime </span>= currTime +<span style="color: #000000;"> timeToCall;
            </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> id;
        };
    };
    
    </span><span style="color: #0000ff;">if</span> (!<span style="color: #000000;">window.cancelAnimationFrame){
        window.cancelAnimationFrame </span>= <span style="color: #0000ff;">function</span><span style="color: #000000;">(id){
            clearTimeout(id);
        };
    };
})(); </span><span style="color: #008000;">//</span><span style="color: #008000;"> Setup requestAnimationFrame when it is unavailable.</span>
<span style="color: #000000;">
document.addEventListener(</span>'touchmove', <span style="color: #0000ff;">function</span><span style="color: #000000;"> (e) {
    e.preventDefault();
});

rotate.addEventListener(</span>'touchstart', <span style="color: #0000ff;">function</span><span style="color: #000000;"> (e) {
    </span><span style="color: #0000ff;">if</span> (e.touches.length == 1<span style="color: #000000;">) {
        x0 </span>= e.targetTouches[0<span style="color: #000000;">].clientX;
        y0 </span>= e.targetTouches[0<span style="color: #000000;">].clientY;
        t0 </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> Date().getTime();
    }
});

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

这里动画用的还是 h5 的 requestAnimationFrame 来实现,对于不支持 requestAnimationFrame 的浏览器也做了兼容。

编程还是相当简单的,难点在于参数的调整,要调整速度控制整个转动周期不能过长过短,而最麻烦的应该是最后减速阶段的加速度调整了。最后阶段太快了就没有紧张的气氛,太慢了又会有卡顿的感觉,尤其在国产移动浏览器上表现得更为明显。

 

性能优化

在没性能优化前,在国产的移动浏览器上卡顿得简直瞎了眼,优化是必须的。

常规做法:分层,动态元素与静态元素分离,也就是转盘绝对定位、单独占一个层,这样的话动态元素的变化不会影响到静态元素的布局,减少重绘。这个优化很基础,写页面的时候就已经实施了,可见问题并不在此。另外之前一直听说要用 CSS3d 变换的方法强制使用 GPU 渲染页面提高性能,但是代码上我已经写了 'rotateZ()',应该已经开启了硬件渲染了,不知道问题在哪,如何解决。空想也是无用,赶紧用 chrome developer 测试了一下:

可以看到数据其实还是相当不错的,脚本、渲染和绘图所占时间还比较合理的,但是问题会出在哪里呢?当然是国产浏览器身上了,那怎么去优化呢?再用 chrome 的渲染检测工具看看吧。打开方式:

  

见名知义,第一项可以检测页面的绘制刷新(重绘),第二项显示图层边界,第三项是显示 FPS(Frame per Second,FPS,帧率),这几项重点关注。因为这个是单页应用,所以后面的滚动问题检测和媒体仿真就略过了。启动之后是这样的:

  

最前面的绿色遮罩表示重绘区域,四周还有一圈褐色的表示渲染层,启动动画的时候可以发现在绘制动画时事实上转盘元素在不断重绘,这应该是问题的关键。但怎么解决呢?很多文章在介绍使用 GPU 渲染时候都会提到下面这两句样式:

<span style="color: #800000;">backface-visibility: hidden;
perspective: 1000;</span>

试着用了这两句之后,立马出现了效果,旋转的时候不再出现绿色的重绘框了:

再回到某国产移动浏览器上测试,终于没有那种死活转不起来的感觉了,感觉跟原生应用差不多了,其实原生应用无非也是用了 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 

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn