requestIdleCallback和requestAnimationFrame詳解

頁面流暢與 FPS

頁面是一幀一幀繪制出來的,當(dāng)每秒繪制的幀數(shù)(FPS)達(dá)到 60 時(shí),頁面是流暢的,小于這個值時(shí),用戶會感覺到卡頓。

1s 60幀,所以每一幀分到的時(shí)間是 1000/60 ≈ 16 ms。所以我們書寫代碼時(shí)力求不讓一幀的工作量超過 16ms。

Frame

那么瀏覽器每一幀都需要完成哪些工作?

image

通過上圖可看到,一幀內(nèi)需要完成如下六個步驟的任務(wù):

  • 處理用戶的交互
  • JS 解析執(zhí)行
  • 幀開始。窗口尺寸變更,頁面滾去等的處理
  • requestAnimationFrame(rAF)
  • 布局
  • 繪制

requestIdleCallback

上面六個步驟完成后沒超過 16 ms,說明時(shí)間有富余,此時(shí)就會執(zhí)行 requestIdleCallback 里注冊的任務(wù)。

image

從上圖也可看出,和 requestAnimationFrame 每一幀必定會執(zhí)行不同,requestIdleCallback 是撿瀏覽器空閑來執(zhí)行任務(wù)。

如此一來,假如瀏覽器一直處于非常忙碌的狀態(tài),requestIdleCallback 注冊的任務(wù)有可能永遠(yuǎn)不會執(zhí)行。此時(shí)可通過設(shè)置 timeout (見下面 API 介紹)來保證執(zhí)行。

API

var handle = window.requestIdleCallback(callback[, options])
  • callback:回調(diào),即空閑時(shí)需要執(zhí)行的任務(wù),該回調(diào)函數(shù)接收一個IdleDeadline對象作為入?yún)ⅰF渲?code>IdleDeadline對象包含:
    • didTimeout,布爾值,表示任務(wù)是否超時(shí),結(jié)合 timeRemaining 使用。
    • timeRemaining(),表示當(dāng)前幀剩余的時(shí)間,也可理解為留給任務(wù)的時(shí)間還有多少。
  • options:目前 options 只有一個參數(shù)
    • timeout。表示超過這個時(shí)間后,如果任務(wù)還沒執(zhí)行,則強(qiáng)制執(zhí)行,不必等待空閑。

IdleDeadline對象參考MDN:https://developer.mozilla.org/zh-CN/docs/Web/API/IdleDeadline

示例

requestIdleCallback(myNonEssentialWork, { timeout: 2000 });
?
// 任務(wù)隊(duì)列
const tasks = [
 () => {
   console.log("第一個任務(wù)");
 },
 () => {
   console.log("第二個任務(wù)");
 },
 () => {
   console.log("第三個任務(wù)");
 },
];
?
function myNonEssentialWork (deadline) {
 // 如果幀內(nèi)有富余的時(shí)間,或者超時(shí)
 while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && tasks.length > 0) {
   work();
 }
?
 if (tasks.length > 0)
   requestIdleCallback(myNonEssentialWork);
 }
?
function work () {
 tasks.shift()();
 console.log('執(zhí)行任務(wù)');
}

超時(shí)的情況,其實(shí)就是瀏覽器很忙,沒有空閑時(shí)間,此時(shí)會等待指定的 timeout 那么久再執(zhí)行,通過入?yún)?dealine 拿到的 didTmieout 會為 true,同時(shí) timeRemaining () 返回的也是 0。超時(shí)的情況下如果選擇繼續(xù)執(zhí)行的話,肯定會出現(xiàn)卡頓的,因?yàn)楸厝粫⒁粠臅r(shí)間拉長。

cancelIdleCallback

setTimeout 類似,返回一個唯一 id,可通過 cancelIdleCallback 來取消任務(wù)。

總結(jié)

一些低優(yōu)先級的任務(wù)可使用 requestIdleCallback 等瀏覽器不忙的時(shí)候來執(zhí)行,同時(shí)因?yàn)闀r(shí)間有限,它所執(zhí)行的任務(wù)應(yīng)該盡量是能夠量化,細(xì)分的微任務(wù)(micro task)。

因?yàn)樗l(fā)生在一幀的最后,此時(shí)頁面布局已經(jīng)完成,所以不建議在 requestIdleCallback 里再操作 DOM,這樣會導(dǎo)致頁面再次重繪。DOM 操作建議在 rAF 中進(jìn)行。同時(shí),操作 DOM 所需要的耗時(shí)是不確定的,因?yàn)闀?dǎo)致重新計(jì)算布局和視圖的繪制,所以這類操作不具備可預(yù)測性。

Promise 也不建議在這里面進(jìn)行,因?yàn)?Promise 的回調(diào)屬性 Event loop 中優(yōu)先級較高的一種微任務(wù),會在 requestIdleCallback 結(jié)束時(shí)立即執(zhí)行,不管此時(shí)是否還有富余的時(shí)間,這樣有很大可能會讓一幀超過 16 ms。

額外補(bǔ)充一下window.requestAnimationFrame

在沒有 requestAnimationFrame 方法的時(shí)候,執(zhí)行動畫,我們可能使用 setTimeoutsetInterval 來觸發(fā)視覺變化;但是這種做法的問題是:回調(diào)函數(shù)執(zhí)行的時(shí)間是不固定的,可能剛好就在末尾,或者直接就不執(zhí)行了,經(jīng)常會引起丟幀而導(dǎo)致頁面卡頓。

image

歸根到底發(fā)生上面這個問題的原因在于時(shí)機(jī),也就是瀏覽器要知道何時(shí)對回調(diào)函數(shù)進(jìn)行響應(yīng)。setTimeoutsetInterval 是使用定時(shí)器來觸發(fā)回調(diào)函數(shù)的,而定時(shí)器并無法保證能夠準(zhǔn)確無誤的執(zhí)行,有許多因素會影響它的運(yùn)行時(shí)機(jī),比如說:當(dāng)有同步代碼執(zhí)行時(shí),會先等同步代碼執(zhí)行完畢,異步隊(duì)列中沒有其他任務(wù),才會輪到自己執(zhí)行。并且,我們知道每一次重新渲染的最佳時(shí)間大約是 16.6 ms,如果定時(shí)器的時(shí)間間隔過短,就會造成 過度渲染,增加開銷;過長又會延遲渲染,使動畫不流暢。

requestAnimationFrame 方法不同與 setTimeoutsetInterval,它是由系統(tǒng)來決定回調(diào)函數(shù)的執(zhí)行時(shí)機(jī)的,會請求瀏覽器在下一次重新渲染之前執(zhí)行回調(diào)函數(shù)。無論設(shè)備的刷新率是多少,requestAnimationFrame 的時(shí)間間隔都會緊跟屏幕刷新一次所需要的時(shí)間;例如某一設(shè)備的刷新率是 75 Hz,那這時(shí)的時(shí)間間隔就是 13.3 ms(1 秒 / 75 次)。需要注意的是這個方法雖然能夠保證回調(diào)函數(shù)在每一幀內(nèi)只渲染一次,但是如果這一幀有太多任務(wù)執(zhí)行,還是會造成卡頓的;因此它只能保證重新渲染的時(shí)間間隔最短是屏幕的刷新時(shí)間。

requestAnimationFrame 方法的具體說明可以看 MDN 的相關(guān)文檔,下面通過一個網(wǎng)頁動畫的示例來了解一下如何使用。

let offsetTop = 0;
const div = document.querySelector(".div");
const run = () => {
 div.style.transform = `translate3d(0, ${offsetTop += 10}px, 0)`;
 window.requestAnimationFrame(run);
};
run();

如果想要實(shí)現(xiàn)動畫效果,每一次執(zhí)行回調(diào)函數(shù),必須要再次調(diào)用 requestAnimationFrame 方法;與 setTimeout 實(shí)現(xiàn)動畫效果的方式是一樣的,只不過不需要設(shè)置時(shí)間間隔。

參考文章

網(wǎng)頁渲染性能優(yōu)化 —— 性能優(yōu)化上

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,703評論 2 380