requestAnimationFrame 方法你真的用對了嗎?

requestAnimationFrame 方法讓咱們能夠在下一幀開始時調用指定函數。可是不少人可能不知道,無論三七二十一直接在 requestAnimationFrame 的回調函數裏繪製動畫會有一個問題。是什麼問題呢?要理解這個問題,咱們先要了解 requestAnimationFrame 的一個知識點。javascript

requestAnimationFrame 無論理回調函數

這個知識點就是 requestAnimationFrame 無論理回調函數。這一點在 w3c 中明確說明了。java

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.
w3cgit

即在回調被執行前,屢次調用帶有同一回調函數的 requestAnimationFrame,會致使回調在同一幀中執行屢次。咱們能夠經過一個簡單的例子模擬在同一幀內屢次調用 requestAnimationFrame 的場景:github

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

咱們用連續調用兩次 requestAnimationFrame 模擬在同一幀中調用兩次 requestAnimationFramedom

例子中的 timestamp 是由 requestAnimationFrame 傳給回調函數的,表示回調隊列被觸發的時間。由輸出可知,animation 函數在同一幀內被執行了兩次,即繪製了兩次動畫。然而在同一幀繪製兩次動畫很明顯是多餘的,至關於畫了一幅畫,而後再在這幅畫上再畫上一樣的一幅畫。函數

問題

那麼什麼場景下,requestAnimationFrame 會在一幀內被屢次調用呢?熟悉事件的同窗應該立刻能想到 mousemove, scroll 這類事件。動畫

因此前面咱們提到的問題就是:由於 requestAnimationFrame 無論理回調函數,在滾動、觸摸這類高觸發頻率的事件回調裏,若是調用 requestAnimationFrame 而後繪製動畫,可能會形成多餘的計算和繪製。例如:code

window.addEventListener('scroll', e => {
    window.requestAnimationFrame(timestamp => {
        animation(timestamp)
    })
})

在上面代碼中,scroll 事件可能在一幀內屢次觸發,因此 animation 函數可能會在一幀內重複繪製,形成沒必要要的計算和渲染。隊列

解決方法

對於這種高頻發事件,通常的解決方法是使用節流函數。可是在這裏使用節流函數並不能完美解決問題。由於節流函數是經過時間管理隊列的,而 requestAnimationFrame 的觸發時間是不固定的,在高刷新頻率的顯示屏上時間會小於 16.67ms,頁面若是被推入後臺,時間可能大於 16.67ms。事件

完美的解決方案是經過 requestAnimationFrame 來管理隊列,其思路就是保證 requestAnimationFrame 的隊列裏,一樣的回調函數只有一個。示意代碼以下:

const onScroll = e => {
    if (scheduledAnimationFrame) { return }

    scheduledAnimationFrame = true
    window.requestAnimationFrame(timestamp => {
        scheduledAnimationFrame = false
        animation(timestamp)
    })
}
window.addEventListener('scroll', onScroll)

可是每次都要寫這麼一堆代碼,也有點麻煩。因此我開源了 raf-plus 庫用於解決這個問題,有須要的的同窗能夠用用~

結論

requestAnimationFrame 無論理回調函數隊列,而滾動、觸摸這類高觸發頻率事件的回調可能會在同一幀內觸發屢次。因此正確使用 requestAnimationFrame 的姿式是,在同一幀內可能調用屢次 requestAnimationFrame 時,要管理回調函數,防止重複繪製動畫。

相關文章
相關標籤/搜索