前端优化Web动画(setInterval、setTimeOut、requestAnimationFrame )

1,132 阅读1分钟

“这是我参与更文挑战的第2天,活动详情查看: 更文挑战

用setInterval是每隔n毫秒执行一次;用setTimeOut是延迟n毫秒之后,执行一次
小demo:
设置小方块移动,从最左边开始,每隔100毫秒向右移动10px,最终移到距离一开始500px位置
用setInterval实现:

var oDemo = document.getElementsByClassName('demo')[0];
var timer = setInterval(move, 100);
function move(){
    var l = oDemo.offsetLeft;
    if (l < 500){
        oDemo.style.left = l + 10 + 'px';
    }else {
        oDemo.style.left = '500px';
    }
    
}

用setTimeOut实现:

var oDemo = document.getElementsByClassName('demo')[0];
var time2;
function move(){
    var l = oDemo.offsetLeft;
    if (l < 500){
        oDemo.style.left = l + 10 + 'px';
        setTimeout(move, 100);
    }else {
        oDemo.style.left = '500px';
    }
    
}
move();

效果一致

计时器做动画存在的问题:
1.当前窗口不再动画页面时,计时器仍将继续工作
比如做了一个动画,动画还没播放完,用户已经再看其他页面了,这个动画会依然继续下去,浪费了cpu,如果是在移动端,就体现在手机耗电的影响了

2.回调函数执行耗时,老是排队
比如上面的move函数执行需要200毫秒,设置每隔100毫秒就执行一次,一次move还没完成,另一个请求已经发来了,就出现了排队情况;只有前一个move完成,才能执行后一个,时间的设置就不太准确;排队太多也会导致浏览器崩溃

3.设置动画频率高,过度绘制,出现掉帧
浏览器屏幕自动刷新频率大概是16.7ms/次,1s刷新60次,与设备性能和cpu负载情况有关
浏览器不断刷新,页面不断重绘,我们才看到了动画的效果
如果计时器频率高于浏览器刷新的频率,即使代码执行了,浏览器没有刷新,也是没有显示的,出现掉帧情况,不流畅
举个例子:
比如,小方块原本在最左边,left为0,设置计时器每10ms刷新一次,小方块向右前进10px,浏览器每16.7ms刷新一次
1.当计时器开始工作,第0ms时,小方块在0位置并显示,
2.第10ms时,小方块本该到10px位置,但浏览器未刷新,小方块还在0位置;
3.第16.7ms时,浏览器刷新了,此时显示小方块在10px位置,从0移动到10px处;
4.第20ms时,小方块本该到20px位置,但浏览器未刷新,显示小方块在10px位置;
5.第30ms时,小方块本该到30位置,但浏览器未刷新,显示小方块在10px位置;
6.第16.7*2ms时,浏览器刷新了,小方块从10px处一下子移动到30px位置,跳过了20px位置,即掉帧
在这里插入图片描述
本来按计时器设置的,在黑色字体的123位置都会显示一次小方块
但是由于浏览器刷新频率没有那么高,只有标红的12位置被展示出来
计时器多执行一次,没有显示

html5对此提供了requestAnimationFrame,来解决这个问题

requestAnimationFrame优势:
1.当前窗口不在动画页面时,停止工作
2.浏览器刷新屏幕时自动执行,无需设置时间间隔
3.浏览器内部优化了该方法,没有优化计时器

和setTimeOut一样是n毫秒之后再执行,但这个n毫秒,自动设置成浏览器刷新频率,浏览器刷新一次,执行一次,不需要手动设置;
浏览器不刷新,就不执行,没有排队掉帧的情况

requestAnimationFrame使用:
1.req = requestAnimationFrame(cb);屏幕每次绘制时执行回调函数cb
2.cancelAnimationFrame(req);

<div class="demo"></div>
<button id="stop">stop</button>
<script>
    var oDemo = document.getElementsByClassName('demo')[0],
        oStop = document.getElementById('stop');
    var req;
    function move(){
        var l = oDemo.offsetLeft;
        if (l < 1000){
            oDemo.style.left = l + 5 + 'px';
            req = requestAnimationFrame(move);//当页面刷新时执行下一次
        }else {
            oDemo.style.left = '1000px';
        }
        
    }
    move();
    oStop.onclick = function(){
        cancelAnimationFrame(req);
    }//点击时清除req
</script>

在这里插入图片描述
有的浏览器不兼容使用requestAnimationFrame,就需要这样写:

window.requestAnimationFrame = (function (callbask) {
    return window.requestAnimationFrame ||
        window.WebKitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.oRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        function () {
            window.setTimeout(callback, 1000 / 60)
        }
    })();//立即执行

如果各个浏览器都不支持,就使用setTimeOut

对应的清除计时器的兼容性写法:

window.cancelAnimationFrame = (function (timer) {
    return window.cancelAnimationFrame ||
        window.WebKitCancelAnimationFrame ||
        window.mozCancelAnimationFrame ||
        window.oCancelAnimationFrame ||
        window.msCancelAnimationFrame ||
        function () {
            window.clearTimeout(timer)
        }
})();