requestAnimationFrame
方法讓我們可以在下一幀開始時調(diào)用指定函數(shù)路操。但是很多人可能不知道,不管三七二十一直接在 requestAnimationFrame
的回調(diào)函數(shù)里繪制動畫會有一個問題渠脉。是什么問題呢?要理解這個問題瓶佳,我們先要了解 requestAnimationFrame
的一個知識點。
requestAnimationFrame 不管理回調(diào)函數(shù)
這個知識點就是 requestAnimationFrame
不管理回調(diào)函數(shù)鳞青。這一點在 w3c 中明確說明了霸饲。
Also note that multiple calls to requestAnimationFrame with the same callback (before callbacks are invoked and the list is cleared) will result in multiple entries being in the list with that same callback, and thus will result in that callback being invoked more than once for the animation frame.
— w3c
即在回調(diào)被執(zhí)行前,多次調(diào)用帶有同一回調(diào)函數(shù)的 requestAnimationFrame
臂拓,會導(dǎo)致回調(diào)在同一幀中執(zhí)行多次厚脉。我們可以通過一個簡單的例子模擬在同一幀內(nèi)多次調(diào)用 requestAnimationFrame
的場景:
const animation = timestamp => console.log('animation called at', timestamp)
window.requestAnimationFrame(animation)
window.requestAnimationFrame(animation)
// animation called at 320.7559999991645
// animation called at 320.7559999991645
我們用連續(xù)調(diào)用兩次 requestAnimationFrame
模擬在同一幀中調(diào)用兩次 requestAnimationFrame
。
例子中的 timestamp
是由 requestAnimationFrame
傳給回調(diào)函數(shù)的胶惰,表示回調(diào)隊列被觸發(fā)的時間傻工。由輸出可知,animation
函數(shù)在同一幀內(nèi)被執(zhí)行了兩次孵滞,即繪制了兩次動畫中捆。然而在同一幀繪制兩次動畫很明顯是多余的,相當(dāng)于畫了一幅畫坊饶,然后再在這幅畫上再畫上同樣的一幅畫泄伪。
問題
那么什么場景下,requestAnimationFrame
會在一幀內(nèi)被多次調(diào)用呢匿级?熟悉事件的同學(xué)應(yīng)該馬上能想到 mousemove
, scroll
這類事件蟋滴。
所以前面我們提到的問題就是:因為 requestAnimationFrame
不管理回調(diào)函數(shù),在滾動痘绎、觸摸這類高觸發(fā)頻率的事件回調(diào)里津函,如果調(diào)用 requestAnimationFrame
然后繪制動畫,可能會造成多余的計算和繪制孤页。例如:
window.addEventListener('scroll', e => {
window.requestAnimationFrame(timestamp => {
animation(timestamp)
})
})
在上面代碼中尔苦,scroll
事件可能在一幀內(nèi)多次觸發(fā),所以 animation
函數(shù)可能會在一幀內(nèi)重復(fù)繪制,造成不必要的計算和渲染蕉堰。
解決方法
對于這種高頻發(fā)事件凌净,一般的解決方法是使用節(jié)流函數(shù)。但是在這里使用節(jié)流函數(shù)并不能完美解決問題屋讶。因為節(jié)流函數(shù)是通過時間管理隊列的冰寻,而 requestAnimationFrame
的觸發(fā)時間是不固定的,在高刷新頻率的顯示屏上時間會小于 16.67ms皿渗,頁面如果被推入后臺斩芭,時間可能大于 16.67ms。
完美的解決方案是通過 requestAnimationFrame
來管理隊列乐疆,其思路就是保證 requestAnimationFrame
的隊列里划乖,同樣的回調(diào)函數(shù)只有一個。示意代碼如下:
const onScroll = e => {
if (scheduledAnimationFrame) { return }
scheduledAnimationFrame = true
window.requestAnimationFrame(timestamp => {
scheduledAnimationFrame = false
animation(timestamp)
})
}
window.addEventListener('scroll', onScroll)
但是每次都要寫這么一堆代碼挤土,也有點麻煩琴庵。所以我開源了 raf-plus 庫用于解決這個問題,有需要的的同學(xué)可以用用~
結(jié)論
requestAnimationFrame
不管理回調(diào)函數(shù)隊列仰美,而滾動迷殿、觸摸這類高觸發(fā)頻率事件的回調(diào)可能會在同一幀內(nèi)觸發(fā)多次。所以正確使用 requestAnimationFrame
的姿勢是咖杂,在同一幀內(nèi)可能調(diào)用多次 requestAnimationFrame
時庆寺,要管理回調(diào)函數(shù),防止重復(fù)繪制動畫诉字。