requestAnimationFrame詳解

爲什麼要說它,源於看到的一道面試題:問題是用js實現一個無限循環的動畫。

首先想到的是定時器

<!doctype html>
<html lang="en">
<head>
    <title>Document</title>
    <style>
        #e{
            width: 100px;
            height: 100px;
            background: red;
            position: absolute;
            left: 0;
            top: 0;
            zoom: 1;
        }
    </style>
</head>
<body>
<div id="e"></div>
<script>


    var e = document.getElementById("e");
    var flag = true;
    var left = 0;

    function render() {
        if(flag == true){
            if(left>=100){
                flag = false
            }
            e.style.left = ` ${left++}px`
        }else{
            if(left<=0){
                flag = true
            }
            e.style.left = ` ${left--}px`
        }
    }
    setInterval(function(){
         render()
    },1000/60)

</script>
</body>
</html>

可以說是完美實現!

至於時間間隔爲什麼是1000/60,這是因爲大多數屏幕渲染的時間間隔是每秒60幀。

既然setInterval可以搞定爲啥還要用requestAnimationFrame呢?最直觀的感覺就是,添加api的人是個大神級牛人,我只能懷疑自己。

所以搜索相關問題發現以下兩點

requestAnimationFrame 比起 setTimeout、setInterval的優勢主要有兩點:

1、requestAnimationFrame 會把每一幀中的所有DOM操作集中起來,在一次重繪或迴流中就完成,並且重繪或迴流的時間間隔緊緊跟隨瀏覽器的刷新頻率,一般來說,這個頻率爲每秒60幀。
2、在隱藏或不可見的元素中,requestAnimationFrame將不會進行重繪或迴流,這當然就意味着更少的的cpu,gpu和內存使用量。

直接上代碼:

<!doctype html>
<html lang="en">
<head>
    <title>Document</title>
    <style>
        #e{
            width: 100px;
            height: 100px;
            background: red;
            position: absolute;
            left: 0;
            top: 0;
            zoom: 1;
        }
    </style>
</head>
<body>
<div id="e"></div>
<script>


    var e = document.getElementById("e");
    var flag = true;
    var left = 0;

    function render() {
        if(flag == true){
            if(left>=100){
                flag = false
            }
            e.style.left = ` ${left++}px`
        }else{
            if(left<=0){
                flag = true
            }
            e.style.left = ` ${left--}px`
        }
    }

    //requestAnimationFrame效果
    (function animloop() {
        render();
        window.requestAnimationFrame(animloop);
    })();

</script>
</body>
</html>

我沒有添加各個瀏覽器的兼容寫法,這裏只說用法。

效果是實現了,不過我想到兩個問題。

1、怎麼停止requestAnimationFrame?是否有類似clearInterval這樣的類似方法?

第一個問題:答案是確定的 必須有:cancelAnimationFrame()接收一個參數 requestAnimationFrame默認返回一個id,cancelAnimationFrame只需要傳入這個id就可以停止了。

<!doctype html>
<html lang="en">
<head>
    <title>Document</title>
    <style>
        #e{
            width: 100px;
            height: 100px;
            background: red;
            position: absolute;
            left: 0;
            top: 0;
            zoom: 1;
        }
    </style>
</head>
<body>
<div id="e"></div>
<script>


    var e = document.getElementById("e");
    var flag = true;
    var left = 0;
    var rafId = null


    function render() {
        if(flag == true){
            if(left>=100){
                flag = false
            }
            e.style.left = ` ${left++}px`
        }else{
            if(left<=0){
                flag = true
            }
            e.style.left = ` ${left--}px`
        }
    }

    //requestAnimationFrame效果
    (function animloop(time) {
        console.log(time,Date.now())
        render();
        rafId = requestAnimationFrame(animloop);
        //如果left等於50 停止動畫
        if(left == 50){
            cancelAnimationFrame(rafId)
        }
    })();

    //setInterval效果
    // setInterval(function(){
    //     render()
    // },1000/60)

</script>
</body>
</html>

2019-10-10 at 10.19.07.gif

附上一個效果圖。也可直接capy代碼測試。

2、如果我想動畫頻率降低怎麼做,爲什麼不考慮加快呵呵 當前刷新頻率已經是屏幕的刷新頻率了再快也沒有意義了

這個略微麻煩點

默認情況下,requestAnimationFrame執行頻率是1000/60,大概是16ms多執一次。

如果我們想每50ms執行一次怎麼辦呢?

requestAnimationFrame執行條件類似遞歸調用 (說的是類似)別咬我,既然這樣的話我們能否自定一個時間間隔再執行呢?當然定時器這麼low的東西我們就不考慮了,都已經拋棄它用rAF了(都快結束了我纔想起寫簡寫太他媽長了),
這個思路來源於我幾年前搞IM的一個項目,服務端推送消息爲了減小包的大小不給時間戳,這個我們做前端的都知道,我們雖然很牛逼 不過用戶更牛逼,萬一改了時間就不好玩了。

解決方案是 當和服務端通信時 記錄下一個時間差,(時間差等於服務端時間-本地時間)不管正負我們只要這個時間差。這樣每當我們接受到消息 或者發送消息的時候我們就拿本地時間和是價差相加。這樣就可以保證和服務端時間是一致的了,思路是不是很牛逼哈哈。

撤了半天我們通過以上思路來解決下rAF改變間隔的問題

上代碼

<!doctype html>
<html lang="en">
<head>
    <title>Document</title>
    <style>
        #e{
            width: 100px;
            height: 100px;
            background: red;
            position: absolute;
            left: 0;
            top: 0;
            zoom: 1;
        }
    </style>
</head>
<body>
<div id="e"></div>
<script>


    var e = document.getElementById("e");
    var flag = true;
    var left = 0;
    //當前執行時間
    var nowTime = 0;
    //記錄每次動畫執行結束的時間
    var lastTime = Date.now();
    //我們自己定義的動畫時間差值
    var diffTime = 40;

    function render() {
        if(flag == true){
            if(left>=100){
                flag = false
            }
            e.style.left = ` ${left++}px`
        }else{
            if(left<=0){
                flag = true
            }
            e.style.left = ` ${left--}px`
        }
    }

    //requestAnimationFrame效果
    (function animloop() {
        //記錄當前時間
        nowTime = Date.now()
        // 當前時間-上次執行時間如果大於diffTime,那麼執行動畫,並更新上次執行時間
        if(nowTime-lastTime > diffTime){
            lastTime = nowTime
            render();
        }
        requestAnimationFrame(animloop);

    })()
</script>
</body>
</html>

附上一個效果:2019-10-10 at 10.58.30.gif

到此結束了。歡迎吐槽!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章