Chrome 88 版 (2021 年 1 月) 會於特定情況下,大幅限制鏈結 JavaScript 計時器的隱藏頁面。這麼做可以降低 CPU 使用率,同時降低電池用量。在某些極端案例中,這項變更會改變行為,但計時器經常會在不同的 API 提高效率且更可靠的情況下使用。
好,那真的很難接受術語,有點模糊讓我們深入瞭解...
術語
隱藏的頁面
一般來說,「隱藏」表示其他分頁正在使用中,或視窗已縮到最小,但瀏覽器可能會將網頁內容完全隱藏時視為隱藏狀態。有些瀏覽器比其他瀏覽器更詳細,但您可以隨時使用頁面瀏覽權限 API 追蹤瀏覽器是否認為瀏覽權限有所變更。
JavaScript 計時器
依「JavaScript 計時器」是指 setTimeout
和 setInterval
,可讓您安排日後的回呼時間。計時器很實用,不會離開,但有時候會在事件更有效且更準確時,用來輪詢狀態。
鏈結計時器
如果在與 setTimeout
回呼相同的工作中呼叫 setTimeout
,則第二個叫用為「鏈結」。使用 setInterval
時,每個疊代都是鏈結的一部分。如果編寫程式碼,可能會比較容易理解:
let chainCount = 0;
setInterval(() => {
chainCount++;
console.log(`This is number ${chainCount} in the chain`);
}, 500);
且:
let chainCount = 0;
function setTimeoutChain() {
setTimeout(() => {
chainCount++;
console.log(`This is number ${chainCount} in the chain`);
setTimeoutChain();
}, 500);
}
節流的運作方式
節流會分階段發生:
最低限度
在符合「任一」條件的情況下,排定的計時器就會發生這種情況:
- 網頁已可見。
- 網頁在過去 30 秒內發出了雜訊。這可能來自任何音效 API,但無聲的音軌則不在此限。
除非要求的逾時時間少於 4 毫秒,且鏈結計數為 5 以上,在此情況下,逾時設定為 4 毫秒,否則計時器不會進行節流。事實並非如此,瀏覽器已經多年來也做過這一點。
調節
當排定的計時器不適用最低節流,且符合以下「任何」條件時,就會發生上述情況:
- 鏈結計數小於 5。
- 網頁已隱藏不到 5 分鐘。
- 正在使用 WebRTC。具體來說,
RTCPeerConnection
包含「開啟」RTCDataChannel
或「使用中」MediaStreamTrack
。
瀏覽器每秒會檢查這個群組中的計時器一次。由於系統只會檢查每秒一次,因此逾時時間類似的計時器會批次在一起,藉此合併分頁執行程式碼所需的時間。事實並非如此,瀏覽器已有多年的努力發展。
高節流
好的,這是 Chrome 88 的新功能當未設定最低節流或節流條件,且符合下列「所有」條件時,已排定的計時器才會發生劇烈節流:
- 網頁已隱藏超過 5 分鐘。
- 鏈結數量為 5 以上。
- 網頁已設為靜音至少 30 秒。
- 目前未使用 WebRTC。
在這種情況下,瀏覽器會每分鐘檢查這個群組中的計時器一次。與先前類似,這表示計時器會在這些每分鐘檢查中批次一起。
解決方法
通常有更好的替代計時器;或者,計時器可與其他項目結合,對 CPU 和電池續航力更友善。
狀態輪詢
這是計時器最常見 (不當) 的使用,用來持續檢查或輪詢 (以瞭解是否有變更)。在大多數情況下,系統之間會執行對應的push作業,其中項目會指出變更發生的時間,因此您不必持續檢查。看看是否有事件所達成的相同目的。
以下提供一些例子:
- 如果需要知道元素何時在可視區域中進入,請使用
IntersectionObserver
。 - 如果您需要知道元素大小變更的時機,請使用
ResizeObserver
。 - 如果您需要知道 DOM 何時變更,請使用
MutationObserver
或自訂元素生命週期回呼。 - 與其輪詢伺服器,請考慮改用網路通訊端、伺服器傳送事件、推送訊息或擷取串流。
- 如果您需要回應音訊/影片的階段變更,請使用
timeupdate
和ended
等事件;如果需要對每個影格執行某些操作,請使用requestVideoFrameCallback
。
如果要在特定時間顯示通知,還可使用通知觸發條件。
動畫
動畫是一種視覺元素,因此不應在頁面「隱藏」時使用 CPU 作業時間。
requestAnimationFrame
排程動畫的運作方式比 JavaScript 計時器更好。這項功能會與裝置的刷新率同步,確保您每個可顯示的影格都只會收到一次回呼,而您將獲得建構該影格的時間上限。此外,requestAnimationFrame
會等待頁面顯示,因此在頁面隱藏時不會使用任何 CPU。
如果您可以預先宣告整個動畫,請考慮使用 CSS 動畫或網頁動畫 API。這些優點與 requestAnimationFrame
相同,但瀏覽器可執行自動合成等其他最佳化功能,且通常更易於使用。
如果動畫的影格速率偏低 (例如閃爍的遊標),計時器仍然是目前最佳選項,但您可以將計時器與 requestAnimationFrame
結合使用,就能同時達到兩者的效果:
function animationInterval(ms, signal, callback) {
const start = document.timeline.currentTime;
function frame(time) {
if (signal.aborted) return;
callback(time);
scheduleFrame(time);
}
function scheduleFrame(time) {
const elapsed = time - start;
const roundedElapsed = Math.round(elapsed / ms) * ms;
const targetNext = start + roundedElapsed + ms;
const delay = targetNext - performance.now();
setTimeout(() => requestAnimationFrame(frame), delay);
}
scheduleFrame(start);
}
使用方式:
const controller = new AbortController();
// Create an animation callback every second:
animationInterval(1000, controller.signal, time => {
console.log('tick!', time);
});
// And stop it:
controller.abort();
測試
這項變更將全面套用至 Chrome 88 版 (2021 年 1 月)。 這項功能目前開放 50% 的 Chrome Beta 版、開發人員和 Canary 版使用者使用。如要進行測試,請在啟動 Chrome Beta 版、開發人員版或 Canary 版時使用以下指令列旗標:
--enable-features="IntensiveWakeUpThrottling:grace_period_seconds/10,OptOutZeroTimeoutTimersFromThrottling,AllowAggressiveThrottlingWithWebSocket"
grace_period_seconds/10
引數會導致在頁面隱藏的 10 秒後 (而不是整整 5 分鐘) 開始過熱節流,可更輕鬆地查看節流的影響。
日後規劃
由於計時器會導致 CPU 用量過大,因此將繼續說明在不破壞網頁內容的情況下,如何進行節流。此外,我們還會根據用途新增/變更 API。我個人不希望使用 animationInterval
,改用有效率的低頻率動畫回呼。如有任何疑問,歡迎透過 Twitter 與我們聯絡!
Heather Zabriskie 透過 Unsplash 提供的標題相片。