深入理解 requestAnimationFrame
在Web應用中,實現動畫效果的方法比較多,Javascript 中可以通過定時器 setTimeout 來實現,css3 可以使用 transition 和 animation 來實現,html5 中的 canvas 也可以實現。除此之外,html5 還提供一個專門用於請求動畫的API,那就是 requestAnimationFrame,顧名思義就是請求動畫幀。為了深入理解 requestAnimationFrame 背後的原理,我們首先需要了解一下與之相關的幾個概念:
1、屏幕刷新頻率
即圖像在屏幕上更新的速度,也即屏幕上的圖像每秒鐘出現的次數,它的單位是赫茲(Hz)。 對於一般筆記本電腦,這個頻率大概是60Hz, 可以在桌面上右鍵->屏幕解析度->高級設置->監視器中查看和設置。這個值的設定受屏幕解析度、屏幕尺寸和顯卡的影響,原則上設置成讓眼睛看著舒適的值都行。
市面上常見的顯示器有兩種,即CRT和LCD, CRT就是傳統顯示器,LCD就是我們常說的液晶顯示器。
CRT是一種使用陰極射線管的顯示器,屏幕上的圖形圖像是由一個個因電子束擊打而發光的熒光點組成,由於顯像管內熒光粉受到電子束擊打後發光的時間很短,所以電子束必須不斷擊打熒光粉使其持續發光。電子束每秒擊打熒光粉的次數就是屏幕刷新頻率。
而對於LCD來說,則不存在刷新頻率的問題,它根本就不需要刷新。因為LCD中每個像素都在持續不斷地發光,直到不發光的電壓改變並被送到控制器中,所以LCD不會有電子束擊打熒光粉而引起的閃爍現象。
因此,當你對著電腦屏幕什麼也不做的情況下,顯示器也會以每秒60次的頻率正在不斷的更新屏幕上的圖像。為什麼你感覺不到這個變化? 那是因為人的眼睛有視覺停留效應,即前一副畫面留在大腦的印象還沒消失,緊接著後一副畫面就跟上來了,這中間只間隔了16.7ms(1000/60≈16.7), 所以會讓你誤以為屏幕上的圖像是靜止不動的。而屏幕給你的這種感覺是對的,試想一下,如果刷新頻率變成1次/秒,屏幕上的圖像就會出現嚴重的閃爍,這樣就很容易引起眼睛疲勞、酸痛和頭暈目眩等癥狀。
2、動畫原理
根據上面的原理我們知道,你眼前所看到圖像正在以每秒60次的頻率刷新,由於刷新頻率很高,因此你感覺不到它在刷新。而動畫本質就是要讓人眼看到圖像被刷新而引起變化的視覺效果,這個變化要以連貫的、平滑的方式進行過渡。那怎麼樣才能做到這種效果呢?
刷新頻率為60Hz的屏幕每16.7ms刷新一次,我們在屏幕每次刷新前,將圖像的位置向左移動一個像素,即1px。這樣一來,屏幕每次刷出來的圖像位置都比前一個要差1px,因此你會看到圖像在移動;由於我們人眼的視覺停留效應,當前位置的圖像停留在大腦的印象還沒消失,緊接著圖像又被移到了下一個位置,因此你才會看到圖像在流暢的移動,這就是視覺效果上形成的動畫。
3、setTimeout
理解了上面的概念以後,我們不難發現,setTimeout 其實就是通過設置一個間隔時間來不斷的改變圖像的位置,從而達到動畫效果的。但我們會發現,利用seTimeout實現的動畫在某些低端機上會出現卡頓、抖動的現象。 這種現象的產生有兩個原因:
setTimeout的執行時間並不是確定的。在Javascript中, setTimeout 任務被放進了非同步隊列中,只有當主線程上的任務執行完以後,才會去檢查該隊列里的任務是否需要開始執行,因此setTimeout 的實際執行時間一般要比其設定的時間晚一些。
刷新頻率受屏幕解析度和屏幕尺寸的影響,因此不同設備的屏幕刷新頻率可能會不同,而 setTimeout只能設置一個固定的時間間隔,這個時間不一定和屏幕的刷新時間相同。
以上兩種情況都會導致setTimeout的執行步調和屏幕的刷新步調不一致,從而引起丟幀現象。 那為什麼步調不一致就會引起丟幀呢?
首先要明白,setTimeout的執行只是在內存中對圖像屬性進行改變,這個變化必須要等到屏幕下次刷新時才會被更新到屏幕上。如果兩者的步調不一致,就可能會導致中間某一幀的操作被跨越過去,而直接更新下一幀的圖像。假設屏幕每隔16.7ms刷新一次,而setTimeout每隔10ms設置圖像向左移動1px, 就會出現如下繪製過程:
第0ms: 屏幕未刷新,等待中,setTimeout也未執行,等待中;
第10ms: 屏幕未刷新,等待中,setTimeout開始執行並設置圖像屬性left=1px;
第16.7ms: 屏幕開始刷新,屏幕上的圖像向左移動了1px, setTimeout 未執行,繼續等待中;
第20ms: 屏幕未刷新,等待中,setTimeout開始執行並設置left=2px;
第30ms: 屏幕未刷新,等待中,setTimeout開始執行並設置left=3px;
第33.4ms:屏幕開始刷新,屏幕上的圖像向左移動了3px, setTimeout未執行,繼續等待中;
…
從上面的繪製過程中可以看出,屏幕沒有更新left=2px的那一幀畫面,圖像直接從1px的位置跳到了3px的的位置,這就是丟幀現象,這種現象就會引起動畫卡頓。
4、requestAnimationFrame
與setTimeout相比,requestAnimationFrame最大的優勢是由系統來決定回調函數的執行時機。具體一點講,如果屏幕刷新率是60Hz,那麼回調函數就每16.7ms被執行一次,如果刷新率是75Hz,那麼這個時間間隔就變成了1000/75=13.3ms,換句話說就是,requestAnimationFrame的步伐跟著系統的刷新步伐走。它能保證回調函數在屏幕每一次的刷新間隔中只被執行一次,這樣就不會引起丟幀現象,也不會導致動畫出現卡頓的問題。
這個API的調用很簡單,如下所示:
除此之外,requestAnimationFrame還有以下兩個優勢:
CPU節能:使用setTimeout實現的動畫,當頁面被隱藏或最小化時,setTimeout 仍然在後台執行動畫任務,由於此時頁面處於不可見或不可用狀態,刷新動畫是沒有意義的,完全是浪費CPU資源。而requestAnimationFrame則完全不同,當頁面處理未激活的狀態下,該頁面的屏幕刷新任務也會被系統暫停,因此跟著系統步伐走的requestAnimationFrame也會停止渲染,當頁面被激活時,動畫就從上次停留的地方繼續執行,有效節省了CPU開銷。
函數節流:在高頻率事件(resize,scroll等)中,為了防止在一個刷新間隔內發生多次函數執行,使用requestAnimationFrame可保證每個刷新間隔內,函數只被執行一次,這樣既能保證流暢性,也能更好的節省函數執行的開銷。一個刷新間隔內函數執行多次時沒有意義的,因為顯示器每16.7ms刷新一次,多次繪製並不會在屏幕上體現出來。
5、優雅降級
由於requestAnimationFrame目前還存在兼容性問題,而且不同的瀏覽器還需要帶不同的前綴。因此需要通過優雅降級的方式對requestAnimationFrame進行封裝,優先使用高級特性,然後再根據不同瀏覽器的情況進行回退,直止只能使用setTimeout的情況。下面的代碼就是有人在github上提供的polyfill,詳細介紹請參考github代碼 requestAnimationFrame
想要系統學習web前端和免費學習資料的 可以加裙六二三九六六八零六
※如何從零開始學Java?
※2017年 7 個最佳的 java 框架
※我!是要成為前端大神的男人!
※2017年web前端大神的筆記,還不趕緊收藏
※2017年JAVA開發工程師項目面試名企重點攻略詳解
TAG:IT技術java交流 |
※從One-hot,Word embedding到Transformer,一步步教你理解Bert
※MapReduce Shuffle深入理解
※Windows PowerShell進階 理解Module
※徹底理解cookie、session、token
※快速理解Token,Cookie,Session
※「straight from the horse s mouth」不是「從馬的嘴」,這樣理解就錯了
※理解 Linux 中的 /etc/services 文件
※阿里CEO張勇:我們理解AI是Alibaba Intelligence
※我所理解的#ShotoniPhone
※我所理解的 Smartisan OS
※理解 OutOfMemoryError 異常
※Cookie,Session和Token概念的正確理解
※Glibc堆漏洞利用基礎-深入理解ptmalloc2 part1
※簡單理解Vue中的nextTick
※Quantic Dream:網易理解我們的願景
※理解foreach, for in, for of 之間的異同
※徹底理解 Node.js 中的回調(Callback)函數
※深入理解 Web Server 原理與實踐:Nginx
※深入理解 ES Modules
※Waymo軟體工程總監Sacha Arnoud:Waymo還需在應用場景和語義理解方面繼續突破