requestAnimationFrame詳解

為什么要說(shuō)它,源于看到的一道面試題:?jiǎn)栴}是用js實(shí)現(xiàn)一個(gè)無(wú)限循環(huán)的動(dòng)畫(huà)。

首先想到的是定時(shí)器

<!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>

可以說(shuō)是完美實(shí)現(xiàn)!

至于時(shí)間間隔為什么是1000/60,這是因?yàn)榇蠖鄶?shù)屏幕渲染的時(shí)間間隔是每秒60幀。

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

所以搜索相關(guān)問(wèn)題發(fā)現(xiàn)以下兩點(diǎn)

requestAnimationFrame 比起 setTimeout、setInterval的優(yōu)勢(shì)主要有兩點(diǎn):

1、requestAnimationFrame 會(huì)把每一幀中的所有DOM操作集中起來(lái),在一次重繪或回流中就完成,并且重繪或回流的時(shí)間間隔緊緊跟隨瀏覽器的刷新頻率,一般來(lái)說(shuō),這個(gè)頻率為每秒60幀。
2、在隱藏或不可見(jiàn)的元素中,requestAnimationFrame將不會(huì)進(jìn)行重繪或回流,這當(dāng)然就意味著更少的的cpu,gpu和內(nèi)存使用量。

直接上代碼:

<!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>

我沒(méi)有添加各個(gè)瀏覽器的兼容寫(xiě)法,這里只說(shuō)用法。

效果是實(shí)現(xiàn)了,不過(guò)我想到兩個(gè)問(wèn)題。

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

第一個(gè)問(wèn)題:答案是確定的 必須有:cancelAnimationFrame()接收一個(gè)參數(shù) requestAnimationFrame默認(rèn)返回一個(gè)id,cancelAnimationFrame只需要傳入這個(gè)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 停止動(dòng)畫(huà)
        if(left == 50){
            cancelAnimationFrame(rafId)
        }
    })();

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

</script>
</body>
</html>
2019-10-10 at 10.19.07.gif

附上一個(gè)效果圖。也可直接capy代碼測(cè)試。

2、如果我想動(dòng)畫(huà)頻率降低怎么做,為什么不考慮加快呵呵 當(dāng)前刷新頻率已經(jīng)是屏幕的刷新頻率了再快也沒(méi)有意義了

這個(gè)略微麻煩點(diǎn)

默認(rèn)情況下,requestAnimationFrame執(zhí)行頻率是1000/60,大概是16ms多執(zhí)一次。

如果我們想每50ms執(zhí)行一次怎么辦呢?

requestAnimationFrame執(zhí)行條件類似遞歸調(diào)用 (說(shuō)的是類似)別咬我,既然這樣的話我們能否自定一個(gè)時(shí)間間隔再執(zhí)行呢?當(dāng)然定時(shí)器這么low的東西我們就不考慮了,都已經(jīng)拋棄它用rAF了(都快結(jié)束了我才想起寫(xiě)簡(jiǎn)寫(xiě)太他媽長(zhǎng)了),
這個(gè)思路來(lái)源于我?guī)啄昵案鉏M的一個(gè)項(xiàng)目,服務(wù)端推送消息為了減小包的大小不給時(shí)間戳,這個(gè)我們做前端的都知道,我們雖然很牛逼 不過(guò)用戶更牛逼,萬(wàn)一改了時(shí)間就不好玩了。

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

撤了半天我們通過(guò)以上思路來(lái)解決下rAF改變間隔的問(wèn)題

上代碼

<!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;
    //當(dāng)前執(zhí)行時(shí)間
    var nowTime = 0;
    //記錄每次動(dòng)畫(huà)執(zhí)行結(jié)束的時(shí)間
    var lastTime = Date.now();
    //我們自己定義的動(dòng)畫(huà)時(shí)間差值
    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() {
        //記錄當(dāng)前時(shí)間
        nowTime = Date.now()
        // 當(dāng)前時(shí)間-上次執(zhí)行時(shí)間如果大于diffTime,那么執(zhí)行動(dòng)畫(huà),并更新上次執(zhí)行時(shí)間
        if(nowTime-lastTime > diffTime){
            lastTime = nowTime
            render();
        }
        requestAnimationFrame(animloop);

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

附上一個(gè)效果:
2019-10-10 at 10.58.30.gif

到此結(jié)束了。歡迎吐槽!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容