學習使用API以可預測的方式執行動畫並安排事件

requestAnimationFrame()是一個相對較新的瀏覽器API。它提供一種更可預測的方式來連接到瀏覽器的渲染循環。

目前,所有現代瀏覽器(包括IE 10+)都支援這個API。

它不是專門用於動畫的API,但這是它被最廣泛使用的領域。

JavaScript有一個事件循環(event loop)。它不斷運行以執行JavaScript。

在過去,使用setTimeout()setInterval()執行動畫。你執行一點點動畫,然後使用setTimeout()在幾毫秒後重複執行這段代碼:

const performAnimation = () => {
 //...
 setTimeout(performAnimation, 1000 / 60)
}
setTimeout(performAnimation, 1000 / 60)

或是

const performAnimation = () => {
 //...
}
setInterval(performAnimation, 1000 / 60)

你可以透過獲取timeout或interval的參考並將其清除來停止動畫:

let timer

const performAnimation = () => {
 //...
 timer = setTimeout(performAnimation, 1000 / 60)
}

timer = setTimeout(performAnimation, 1000 / 60)

//...

clearTimeout(timer)

performAnimation()調用之間的1000 / 60間隔取決於顯示器的刷新頻率,大多數情況下是60 Hz(每秒重繪60次),因為由於其限制,如果顯示器無法顯示,執行重繪將是無用的。這導致我們有大約16.6毫秒的時間來顯示每一個幀。

這種方法的問題是,即使我們精確指定了這個間隔,瀏覽器可能忙於執行其他操作,我們的setTimeout調用可能不能及時進行重繪,並且將被延遲到下一個循環。

這很糟糕,因為我們丟失了一個幀,在下一個幀中,動畫將被執行兩次,使眼睛注意到不平滑的動畫。

在Glitch上的這個使用setTimeout()構建的動畫的示例中可以看到這個問題。

requestAnimationFrame是執行動畫的標準方式,儘管代碼看起來非常類似於setTimeout/setInterval的代碼,它的工作方式非常不同:

let request

const performAnimation = () => {
 request = requestAnimationFrame(performAnimation)
 //執行動畫
}

requestAnimationFrame(performAnimation)

//...

cancelAnimationFrame(request) //停止動畫

在Glitch上的這個使用requestAnimationFrame()構建的動畫的示例展示了這個方法。

優化

自從引入requestAnimationFrame()以來,它對CPU友好,如果當前窗口或選項卡不可見,動畫會停止。

在引入requestAnimationFrame()之前,setTimeout/setInterval即使在選項卡隱藏時也運行,但現在,由於這種方法被證明對省電也是成功的,瀏覽器也為這些事件實現了節流,每秒最多允許1次執行。

使用requestAnimationFrame,瀏覽器可以進一步優化資源消耗,使動畫更加流暢。

時間軸示例

這是如果使用setTimeout或setInterval的完美時間軸:

完美時間軸

你擁有一組繪圖(綠色)和渲染(紫色)事件,你的代碼在黃色框中 - 順便提一下,這些顏色在瀏覽器開發工具中也被用來表示時間軸:

開發工具時間軸

這個插圖展示了完美的情況。你每60毫秒進行繪圖和渲染,而你的動畫剛好介於其中,完全規律。

如果你對你的動畫函數使用更高的頻率調用:

太頻繁的時間軸

請注意,在每個幀中,我們呼叫了4個動畫步驟,然後才進行渲染,這將使動畫感覺很不流暢。

如果setTimeout由於其他代碼阻塞事件循環而無法按時運行會怎樣?我們會錯過一個幀:

錯過的幀

如果動畫步驟花費的時間比您預期的時間多一點會怎樣?

時間線上的延遲

渲染和繪圖事件也會被延遲。

這是requestAnimationFrame()在視覺上的運行方式:

requestAnimationFrame時間軸

所有的動畫程式碼在渲染和繪圖事件之前運行。這使得代碼更可預測,並且我們有很多時間來執行動畫,而不需要擔心超過我們有在16毫秒的間隔內執行的時間。

請查看Jake Archibald的這個很棒的關於該主題的視頻