最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

關(guān)于JavaScript實(shí)現(xiàn)動(dòng)畫時(shí)動(dòng)畫抖動(dòng)的原因與解決方法

 更新時(shí)間:2022年06月14日 11:21:04   作者:xy_向陽  
最近在使用JS動(dòng)畫做一些練習(xí)的時(shí)候我發(fā)現(xiàn)在動(dòng)畫執(zhí)行時(shí)間內(nèi)快速移開鼠標(biāo)時(shí)會(huì)出現(xiàn)動(dòng)畫因鼠標(biāo)移動(dòng)過快從而導(dǎo)致代碼沖突讓畫面抖動(dòng)的bug,這篇文章主要給大家介紹了關(guān)于JavaScript實(shí)現(xiàn)動(dòng)畫時(shí)動(dòng)畫抖動(dòng)的原因與解決方法,需要的朋友可以參考下

前段時(shí)間在使用 JavaScript 做動(dòng)畫的時(shí)候發(fā)現(xiàn)做出來的動(dòng)畫會(huì)出現(xiàn)卡頓的現(xiàn)象,今天我們主要就來聊一下卡頓的原因以及如何解決這個(gè)問題。

使用定時(shí)器實(shí)現(xiàn)動(dòng)畫出現(xiàn)卡頓的原因

  • 主要原因是瀏覽器無法確定定時(shí)器的回調(diào)函數(shù)的執(zhí)行時(shí)機(jī)。以 setInterval 為例,當(dāng)一個(gè) setInterval 定時(shí)器被創(chuàng)建后,它的回調(diào)任務(wù)會(huì)被放到異步隊(duì)列,只有當(dāng)同步任務(wù)執(zhí)行完成后,瀏覽器才會(huì)檢查異步隊(duì)列中是否有需要執(zhí)行的異步任務(wù),如果有,就取出執(zhí)行,這樣會(huì)使任務(wù)的實(shí)際執(zhí)行時(shí)機(jī)比所設(shè)定的延遲時(shí)間要晚一些。

這個(gè)問題跟瀏覽器的事件循環(huán)機(jī)制有關(guān),JavaScript 引擎在解析執(zhí)行我們的代碼的時(shí)候,遇到定時(shí)器,會(huì)調(diào)用瀏覽器 API,讓定時(shí)器去進(jìn)行倒計(jì)時(shí),此時(shí)并不阻塞同步代碼的執(zhí)行,當(dāng)定時(shí)器倒計(jì)時(shí)完畢,定時(shí)器回調(diào)會(huì)被放入宏任務(wù)隊(duì)列等待執(zhí)行。

在這個(gè)過程中問題就來了,如果說同步代碼的執(zhí)行需要50ms,而定時(shí)器設(shè)置的定時(shí)只有20ms,那么由于事件循環(huán)的機(jī)制,還是要等待同步任務(wù)執(zhí)行完整之后再來執(zhí)行微任務(wù)隊(duì)列中的定時(shí)器回調(diào),而這中間,又相隔了30ms,在這30ms的過程中,定時(shí)器的回調(diào)一直處于 pendding 的狀態(tài)。如果定時(shí)器中是動(dòng)畫相關(guān)的操作,那也需要在預(yù)期的時(shí)間上多等待50ms。

畫了張圖,希望能幫助大家理解(如果不能幫助大家理解,那么請忽略這張圖……)

  • 屏幕分辨率和尺寸也會(huì)影響刷新頻率,不同設(shè)備的屏幕繪制頻率可能會(huì)有所不同,而 setInterval 只能設(shè)置一個(gè)固定的時(shí)間間隔,這個(gè)間隔時(shí)間不一定與屏幕的刷新時(shí)間同步,所以就可能會(huì)導(dǎo)致動(dòng)畫出現(xiàn)隨機(jī)丟幀的問題。

這里有兩個(gè)點(diǎn),一個(gè)是顯示器的刷新頻率,另一個(gè)是定時(shí)器的時(shí)間間隔。

一般顯示器刷新頻率都是60Hz,這基本上意味著每秒需要重繪60次。大多數(shù)瀏覽器都會(huì)限制重繪的頻率,使其不超過顯示器的刷新頻率。因?yàn)槌^刷新頻率,用戶也感知不到,白白浪費(fèi)性能。

因此,實(shí)現(xiàn)平滑動(dòng)畫最佳的重繪間隔為1000ms/60,大約17毫秒。以這個(gè)速度重繪,可以實(shí)現(xiàn)最平滑的動(dòng)畫效果。因?yàn)檫@已經(jīng)是瀏覽器的重繪頻率的極限了。

知道何時(shí)繪制下一幀,是創(chuàng)造平滑動(dòng)畫的關(guān)鍵。直到幾年前,都沒有確切保證讓瀏覽器在何時(shí)把下一幀繪制出來的方法。隨著 <canvas>HTML5 游戲的興起,開發(fā)者發(fā)現(xiàn) setIntervalsetTimeout 的不精確是個(gè)大問題,而瀏覽器自身的計(jì)時(shí)器也存在著精度不足毫秒的問題。

以下是幾個(gè)瀏覽器計(jì)時(shí)器的精度情況:

  • IE8 以及之前的版本計(jì)時(shí)器精度為 15.625ms;
  • IE9 及之后的版計(jì)時(shí)器精度為 4ms;
  • FireFox 和 Safari 的計(jì)時(shí)器精度約為 10ms;
  • Chrome 的計(jì)時(shí)器精度為 4ms。

以 Chrome 為例,它的計(jì)時(shí)器精度為 4ms,這意味著 0~4 之間的任何值最終要么是 0,要么是4;不可能是別的值。因此,即使將瀏覽器的時(shí)間間隔設(shè)置為最優(yōu),也免不了只能得到相近似的結(jié)果。

對(duì)于 JavaScript 來說,它不知道瀏覽器會(huì)在何時(shí)發(fā)生重繪。因此,我們通過定時(shí)器做動(dòng)畫的時(shí)候,在定時(shí)器中控制動(dòng)畫的代碼已經(jīng)執(zhí)行完成的情況下,動(dòng)畫效果并不一定會(huì)立馬生效,因?yàn)榇藭r(shí)瀏覽器可能還處在等待下一次重繪的過程中,當(dāng)下一次重繪完成,動(dòng)畫效果才能在瀏覽器窗口中顯示出來。

而由于瀏覽器計(jì)時(shí)器時(shí)間差的問題,會(huì)導(dǎo)致定時(shí)器的計(jì)時(shí)并不一定是我們設(shè)置的 17 ms,而是在多個(gè)時(shí)間點(diǎn)內(nèi)反復(fù)橫跳,也因此才出現(xiàn)使用定時(shí)器做動(dòng)畫的時(shí)候動(dòng)畫抖動(dòng)的問題,在復(fù)雜的動(dòng)畫中,這種問題尤為明顯。

在這樣的環(huán)境下,今天的主角 requestAnimationFrame 應(yīng)運(yùn)而生!

requestAnimationFrame 的前世今生

Mozilla 的 Robert O'Callahan 一直在思考這個(gè)問題,并且提出了一個(gè)獨(dú)特的解決方案。他指出,瀏覽器知道 CSS 過渡和動(dòng)畫應(yīng)該什么時(shí)候開始,并且能夠計(jì)算出正確的時(shí)間間隔,到時(shí)間就去刷新用戶界面。

但是對(duì)于 JavaScript 而言,瀏覽器并不知道動(dòng)畫什么時(shí)候開始。他給出的方案是創(chuàng)造一個(gè)名為 MozRequestAnimationFrame 的新方法,以此來通知瀏覽器某些 JavaScript 代碼要執(zhí)行動(dòng)畫了。這樣瀏覽器就可以在運(yùn)行某些代碼后進(jìn)行適當(dāng)?shù)膬?yōu)化。

目前,所有的瀏覽器都支持這個(gè)方法不帶前綴的版本,也就是現(xiàn)在用到的 requestAnimationFrame。

requestAnimationFrame VS setInterval

這里就不再過多的介紹 requestAnimationFrame 的詳細(xì)用法了,它的用法并不復(fù)雜。

與定時(shí)器不同的是,requestAnimationFrame 只會(huì)在被調(diào)用的時(shí)候執(zhí)行一次動(dòng)畫,而不會(huì)連續(xù)執(zhí)行。如果想做連續(xù)的動(dòng)畫,則可以通過遞歸來實(shí)現(xiàn)對(duì) requestAnimationFrame 的連續(xù)調(diào)用。

接下來通過一個(gè) demo 來對(duì)比一下使用 requestAnimationFramesetInterval 兩者做出來的動(dòng)畫效果之間的差異。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <style>
    .square1,
    .square2 {
      position: absolute;
      width: 100px;
      height: 100px;
    }
    .square1 {
      top: 40px;
      background: red;
    }
    .square2 {
      top: 150px;
      background: blue;
    }
  </style>
  <body>
    <div class="container">
      <div class="square1"></div>
      <div class="square2"></div>
      <button class="btn">開始!</button>
    </div>
    <script>
      const square1El = document.querySelector('.square1')
      const square2El = document.querySelector('.square2')
      // 定時(shí)器版
      function squareMove() {
        let timer = null
        square1El.style.left = '0px'
        timer = setInterval(() => {
          const squareLeft = parseInt(square1El.style.left)
          if (squareLeft >= 500) return clearInterval(timer)
          square1El.style.left = squareLeft + 1 + 'px'
        }, 17)
      }

      // requestAnimationFrame 版
      function squareMove2() {
        let timer = null
        square2El.style.left = '0px'

        function updateAnimation() {
          const squareLeft = parseInt(square2El.style.left)
          if (squareLeft >= 500) return cancelAnimationFrame(timer)
          square2El.style.left = squareLeft + 1 + 'px'
          window.requestAnimationFrame(updateAnimation)
        }

        window.requestAnimationFrame(updateAnimation)
      }

      document.querySelector('.btn').addEventListener('click', () => {
        squareMove()
        squareMove2()
      })
    </script>
  </body>
</html>

在頁面中畫了兩個(gè)正方形,當(dāng)點(diǎn)擊按鈕的時(shí)候方塊開始運(yùn)動(dòng),紅色方塊是使用 setInterval 實(shí)現(xiàn)的動(dòng)畫,藍(lán)色方塊使用的是 requestAnimationFrame。

接下來看一下實(shí)現(xiàn)的效果。

在生成gif的時(shí)候視頻被壓縮了,但是還是能看到紅色的方塊在開始運(yùn)動(dòng)的時(shí)候有明顯的抖動(dòng),而藍(lán)色的方塊則比較絲滑。

實(shí)際上,requestAnimationFrame 的回調(diào)函數(shù)可以接收一個(gè)參數(shù),這個(gè)參數(shù)是一個(gè) DOMHightResTimeStamp 的實(shí)例(比如:performance.now()的返回值),用來表示下一次重繪的時(shí)間。這一點(diǎn)非常重要,requestAnimationFrame 實(shí)際上是把重繪任務(wù)安排在了未來的一個(gè)已知的時(shí)間點(diǎn)上,而且通過這個(gè)參數(shù)來告訴開發(fā)者。

類似于 setInterval 的清除方法 clearInterval,requestAnimationFrame 也有對(duì)應(yīng)的取消重繪的方法 cancelAnimationFrame,用法也跟 clearInterval 非常類似,在每次調(diào)用 requestAnimationFrame 的時(shí)候,都會(huì)返回一個(gè)id,cancelAnimationFrame 就是通過這個(gè)id去取消對(duì)應(yīng)的 requestAnimationFrame。

看到這里,大家應(yīng)該對(duì) setIntervalrequestAnimationFrame 都有了更深的了解,以后使用 JavaScript 做動(dòng)畫還是以 requestAnimationFrame 為主。

希望講解的內(nèi)容能對(duì)大家有所幫助~

參考資料

[1]《JavaScript高級(jí)程序設(shè)計(jì)(第四版)》第18章第1節(jié)。

總結(jié)

到此這篇關(guān)于JavaScript實(shí)現(xiàn)動(dòng)畫時(shí)動(dòng)畫抖動(dòng)的原因與解決方法的文章就介紹到這了,更多相關(guān)JS動(dòng)畫抖動(dòng)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • JS中超越現(xiàn)實(shí)的匿名函數(shù)用法實(shí)例分析

    JS中超越現(xiàn)實(shí)的匿名函數(shù)用法實(shí)例分析

    這篇文章主要介紹了JS中超越現(xiàn)實(shí)的匿名函數(shù)用法,結(jié)合實(shí)例形式分析了javascript匿名函數(shù)定義、用法及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下
    2019-06-06
  • JavaScript數(shù)組基于交換的排序示例【冒泡排序】

    JavaScript數(shù)組基于交換的排序示例【冒泡排序】

    這篇文章主要介紹了JavaScript數(shù)組基于交換的排序,結(jié)合實(shí)例形式分析了JavaScript排序算法中的冒泡排序簡單實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2018-07-07
  • 淺談JavaScript中this的指向更改

    淺談JavaScript中this的指向更改

    這篇文章主要介紹了淺談JavaScript中this的指向更改,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • js如何根據(jù)id刪除數(shù)組中對(duì)象

    js如何根據(jù)id刪除數(shù)組中對(duì)象

    這篇文章主要介紹了js如何根據(jù)id刪除數(shù)組中對(duì)象的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • JS實(shí)現(xiàn)在網(wǎng)頁中彈出一個(gè)輸入框的方法

    JS實(shí)現(xiàn)在網(wǎng)頁中彈出一個(gè)輸入框的方法

    這篇文章主要介紹了JS實(shí)現(xiàn)在網(wǎng)頁中彈出一個(gè)輸入框的方法,實(shí)例分析了prompt的用法,可用來設(shè)置密碼,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-03-03
  • 推薦20家國外的腳本下載網(wǎng)站

    推薦20家國外的腳本下載網(wǎng)站

    腳本使網(wǎng)站更加動(dòng)態(tài)和更具交互性,但是寫好一個(gè)腳本并不是一件容易的工作,因此一些開發(fā)者會(huì)到網(wǎng)站下載其他人分享的腳本來使用。今天,本文向大家推薦20佳國外的腳本下載網(wǎng)站。
    2011-04-04
  • 淺述節(jié)點(diǎn)的創(chuàng)建及常見功能的實(shí)現(xiàn)

    淺述節(jié)點(diǎn)的創(chuàng)建及常見功能的實(shí)現(xiàn)

    本文主要對(duì)節(jié)點(diǎn)的創(chuàng)建及常見功能的實(shí)現(xiàn)方法進(jìn)行介紹,希望會(huì)對(duì)大家學(xué)習(xí)javascript有所幫助,下面就跟小編一起來看下吧
    2016-12-12
  • Js數(shù)組的操作push,pop,shift,unshift等方法詳細(xì)介紹

    Js數(shù)組的操作push,pop,shift,unshift等方法詳細(xì)介紹

    js中針對(duì)數(shù)組操作的方法還是比較多的,今天突然想到來總結(jié)一下,也算是溫故而知新吧。不過不會(huì)針對(duì)每個(gè)方法進(jìn)行講解,我只是選擇其中的一些來講,感興趣的朋友可以研究一下
    2012-12-12
  • es6中some和every方法使用簡單示例

    es6中some和every方法使用簡單示例

    JavaScript在ES6版本后提供了一些更加便捷的方法供開發(fā)者使用,實(shí)現(xiàn)原理其實(shí)是在對(duì)應(yīng)的構(gòu)造函數(shù)原型提供方法,下面這篇文章主要給大家介紹了關(guān)于es6中some和every方法使用的相關(guān)資料,需要的朋友可以參考下
    2023-04-04
  • JS 對(duì)輸入框進(jìn)行限制(常用的都有)

    JS 對(duì)輸入框進(jìn)行限制(常用的都有)

    本文為大家介紹下使用js對(duì)輸入框進(jìn)行限制:文本框只能輸入數(shù)字代碼、只能輸入數(shù)字、只能輸入字母和漢字、只能輸入英文字母和數(shù)字等等,還有很多,感興趣的可以學(xué)習(xí)下
    2013-07-07

最新評(píng)論

武胜县| 甘肃省| 宜都市| 寻甸| 罗源县| 稷山县| 京山县| 马关县| 凤台县| 徐闻县| 九寨沟县| 阆中市| 阿拉尔市| 靖安县| 廉江市| 横山县| 台南县| 神池县| 丰原市| 台东县| 丹凤县| 大荔县| 托克逊县| 平乐县| 英山县| 揭东县| 利川市| 德惠市| 江川县| 双峰县| 奈曼旗| 洪雅县| 珲春市| 故城县| 弥勒县| 抚顺市| 荥经县| 灵寿县| 师宗县| 勐海县| 汾阳市|