<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
前段時間在使用 JavaScript 做動畫的時候發現做出來的動畫會出現卡頓的現象,今天我們主要就來聊一下卡頓的原因以及如何解決這個問題。
setInterval
為例,當一個 setInterval
定時器被建立後,它的回撥任務會被放到非同步佇列,只有當同步任務執行完成後,瀏覽器才會檢查非同步佇列中是否有需要執行的非同步任務,如果有,就取出執行,這樣會使任務的實際執行時機比所設定的延遲時間要晚一些。這個問題跟瀏覽器的事件迴圈機制有關,JavaScript 引擎在解析執行我們的程式碼的時候,遇到定時器,會呼叫瀏覽器 API,讓定時器去進行倒計時,此時並不阻塞同步程式碼的執行,當定時器倒計時完畢,定時器回撥會被放入宏任務佇列等待執行。
在這個過程中問題就來了,如果說同步程式碼的執行需要50ms,而定時器設定的定時只有20ms,那麼由於事件迴圈的機制,還是要等待同步任務執行完整之後再來執行微任務佇列中的定時器回撥,而這中間,又相隔了30ms,在這30ms的過程中,定時器的回撥一直處於 pendding 的狀態。如果定時器中是動畫相關的操作,那也需要在預期的時間上多等待50ms。
畫了張圖,希望能幫助大家理解(如果不能幫助大家理解,那麼請忽略這張圖……)
setInterval
只能設定一個固定的時間間隔,這個間隔時間不一定與螢幕的重新整理時間同步,所以就可能會導致動畫出現隨機丟幀的問題。這裡有兩個點,一個是顯示器的重新整理頻率,另一個是定時器的時間間隔。
一般顯示器重新整理頻率都是60Hz,這基本上意味著每秒需要重繪60次。大多數瀏覽器都會限制重繪的頻率,使其不超過顯示器的重新整理頻率。因為超過重新整理頻率,使用者也感知不到,白白浪費效能。
因此,實現平滑動畫最佳的重繪間隔為1000ms/60,大約17毫秒。以這個速度重繪,可以實現最平滑的動畫效果。因為這已經是瀏覽器的重繪頻率的極限了。
知道何時繪製下一幀,是創造平滑動畫的關鍵。直到幾年前,都沒有確切保證讓瀏覽器在何時把下一幀繪製出來的方法。隨著 <canvas>
和 HTML5
遊戲的興起,開發者發現 setInterval
和 setTimeout
的不精確是個大問題,而瀏覽器自身的計時器也存在著精度不足毫秒的問題。
以下是幾個瀏覽器計時器的精度情況:
以 Chrome 為例,它的計時器精度為 4ms,這意味著 0~4 之間的任何值最終要麼是 0,要麼是4;不可能是別的值。因此,即使將瀏覽器的時間間隔設定為最優,也免不了只能得到相近似的結果。
對於 JavaScript 來說,它不知道瀏覽器會在何時發生重繪。因此,我們通過定時器做動畫的時候,在定時器中控制動畫的程式碼已經執行完成的情況下,動畫效果並不一定會立馬生效,因為此時瀏覽器可能還處在等待下一次重繪的過程中,當下一次重繪完成,動畫效果才能在瀏覽器視窗中顯示出來。
而由於瀏覽器計時器時間差的問題,會導致定時器的計時並不一定是我們設定的 17 ms,而是在多個時間點內反覆橫跳,也因此才出現使用定時器做動畫的時候動畫抖動的問題,在複雜的動畫中,這種問題尤為明顯。
在這樣的環境下,今天的主角 requestAnimationFrame
應運而生!
Mozilla 的 Robert O'Callahan 一直在思考這個問題,並且提出了一個獨特的解決方案。他指出,瀏覽器知道 CSS 過渡和動畫應該什麼時候開始,並且能夠計算出正確的時間間隔,到時間就去重新整理使用者介面。
但是對於 JavaScript 而言,瀏覽器並不知道動畫什麼時候開始。他給出的方案是創造一個名為 MozRequestAnimationFrame
的新方法,以此來通知瀏覽器某些 JavaScript 程式碼要執行動畫了。這樣瀏覽器就可以在執行某些程式碼後進行適當的優化。
目前,所有的瀏覽器都支援這個方法不帶字首的版本,也就是現在用到的 requestAnimationFrame
。
這裡就不再過多的介紹 requestAnimationFrame
的詳細用法了,它的用法並不複雜。
與定時器不同的是,requestAnimationFrame
只會在被呼叫的時候執行一次動畫,而不會連續執行。如果想做連續的動畫,則可以通過遞回來實現對 requestAnimationFrame
的連續呼叫。
接下來通過一個 demo 來對比一下使用 requestAnimationFrame
和 setInterval
兩者做出來的動畫效果之間的差異。
<!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') // 定時器版 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>
在頁面中畫了兩個正方形,當點選按鈕的時候方塊開始運動,紅色方塊是使用 setInterval
實現的動畫,藍色方塊使用的是 requestAnimationFrame
。
接下來看一下實現的效果。
在生成gif的時候視訊被壓縮了,但是還是能看到紅色的方塊在開始運動的時候有明顯的抖動,而藍色的方塊則比較絲滑。
實際上,requestAnimationFrame
的回撥函數可以接收一個引數,這個引數是一個 DOMHightResTimeStamp
的範例(比如:performance.now()的返回值),用來表示下一次重繪的時間。這一點非常重要,requestAnimationFrame
實際上是把重繪任務安排在了未來的一個已知的時間點上,而且通過這個引數來告訴開發者。
類似於 setInterval
的清除方法 clearInterval
,requestAnimationFrame
也有對應的取消重繪的方法 cancelAnimationFrame
,用法也跟 clearInterval
非常類似,在每次呼叫 requestAnimationFrame
的時候,都會返回一個id,cancelAnimationFrame
就是通過這個id去取消對應的 requestAnimationFrame
。
看到這裡,大家應該對 setInterval
和 requestAnimationFrame
都有了更深的瞭解,以後使用 JavaScript 做動畫還是以 requestAnimationFrame
為主。
希望講解的內容能對大家有所幫助~
[1]《JavaScript高階程式設計(第四版)》第18章第1節。
到此這篇關於JavaScript實現動畫時動畫抖動的原因與解決方法的文章就介紹到這了,更多相關JS動畫抖動內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45