利用好瀏覽器的空閑時間 --- requestIdleCallback


頁面流暢與 FPS

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

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

Frame

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

image
瀏覽器一幀內的工作

通過上圖可看到,一幀內需要完成如下六個步驟的任務:

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

requestIdleCallback

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

image
requestIdleCallback 在瀏覽器一幀內的位置示意

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

如此一來,假如瀏覽器一直處於非常忙碌的狀態,requestIdleCallback 注冊的任務有可能永遠不會執行。此時可通過設置 timeout (見下面 API 介紹)來保證執行。

API

var handle = window.requestIdleCallback(callback[, options])
  • callback: ():回調即空閑時需要執行的任務,接收一個 IdleDeadline 對象作為入參。其中 IdleDeadline 對象包含:
    • didTimeout,布爾值,表示任務是否超時,結合 timeRemaining 使用。
    • timeRemaining(),表示當前幀剩余的時間,也可理解為留給任務的時間還有多少。
  • options:目前 options 只有一個參數
    • timeout 。表示超過這個時間后,如果任務還沒執行,則強制執行,不必等待空閑。

示例

requestIdleCallback(myNonEssentialWork, { timeout: 2000 });

function myNonEssentialWork (deadline) {

  // 如果幀內有富余的時間,或者超時
  while ((deadline.timeRemaining() > 0 || deadline.didTimeout) &&
         tasks.length > 0)
    doWorkIfNeeded();

  if (tasks.length > 0)
    requestIdleCallback(myNonEssentialWork);
}

超時的情況,其實就是瀏覽器很忙,沒有空閑時間,此時會等待指定的 timeout 那么久再執行,通過入參 dealine 拿到的 didTmieout 會為 true,同時 timeRemaining () 返回的也是 0。超時的情況下如果選擇繼續執行的話,肯定會出現卡頓的,因為必然會將一幀的時間拉長。

cancelIdleCallback

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

總結

一些低優先級的任務可使用 requestIdleCallback 等瀏覽器不忙的時候來執行,同時因為時間有限,它所執行的任務應該盡量是能夠量化,細分的微任務(micro task)。

因為它發生在一幀的最后,此時頁面布局已經完成,所以不建議在 requestIdleCallback 里再操作 DOM,這樣會導致頁面再次重繪。DOM 操作建議在 rAF 中進行。同時,操作 DOM 所需要的耗時是不確定的,因為會導致重新計算布局和視圖的繪制,所以這類操作不具備可預測性。

Promise 也不建議在這里面進行,因為 Promise 的回調屬性 Event loop 中優先級較高的一種微任務,會在 requestIdleCallback 結束時立即執行,不管此時是否還有富余的時間,這樣有很大可能會讓一幀超過 16 ms。

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM