瀏覽器支援
現今的新式瀏覽器有時會在下列情況中將網頁停權或完全捨棄 系統資源有限日後,瀏覽器會 以降低耗電量和記憶體用量Page Lifecycle API 提供生命週期掛鉤,這樣網頁就能安全地處理這些瀏覽器 而不影響使用者體驗。來看看這個 API 查看是否應在應用程式中實作這些功能。
背景
應用程式生命週期是現代化作業系統管理的關鍵方式 再複習一下,機構節點 是所有 Google Cloud Platform 資源的根節點在 Android、iOS 和近期 Windows 版本上,可啟動應用程式 作業系統隨時停止。這讓這些平台能簡化及 並在最有利於使用者的位置重新分配資源
在網路上,向來沒有這樣的生命週期,應用程式可以保留 使應用程式失去生命有大量網頁正在執行時,關鍵系統 記憶體、CPU、電池和網路等資源可能會過度訂閱 導致使用者體驗不佳
網頁平台長期存在與生命週期狀態相關的事件
— 例如 load
、
unload
和
visibilitychange
— 這些事件僅供開發人員使用
來回應使用者啟動的生命週期狀態變更。適合透過網路工作
可以在低功耗裝置上穩定運作 (一般來說
所有平台) 瀏覽器需要主動收回和重新分配系統
再複習一下,機構節點
是所有 Google Cloud Platform 資源的根節點
事實上,瀏覽器目前已經會採取積極措施以節省資源 且許多瀏覽器 (尤其是 Chrome) 會 可以執行更多作業,減少整體資源佔用空間。
問題在於,開發人員無法因應這類技術 系統啟動的介入措施,甚至是知道他們正在演唱。也就是說 瀏覽器必須保守或有破壞網頁的風險。
Page Lifecycle API 可以透過以下方式解決這個問題:
- 在網路上介紹及標準化生命週期狀態的概念。
- 定義系統啟動的新狀態,讓瀏覽器 隱藏或閒置分頁可能會消耗的資源。
- 建立新的 API 和事件,讓網頁開發人員能夠回應 在這些新系統啟動狀態間來回切換。
這項解決方案提供了網頁程式開發人員所需的可預測性 使應用程式不受系統介入影響,同時也能讓瀏覽器執行 積極改善系統資源,最終造福所有網路使用者。
本文的其餘部分將介紹新的「頁面生命週期」功能 探索它們與所有現有網路平台狀態的關係 和事件還會針對各類廣告活動提供建議和最佳做法 開發人員應 (且不該) 在各個狀態下執行的工作。
頁面生命週期狀態和事件總覽
所有頁面生命週期狀態皆為獨立且互斥,亦即網頁 只能處於一個狀態。頁面生命週期狀態的大多數變更 通常可透過 DOM 事件觀察 (例外狀況請參閱開發人員建議針對每個狀態)。
也許是解釋頁面生命週期狀態最簡單的方式, 發出轉換信號的事件,如下圖所示:
州
下表將詳細說明各種狀態。也會列出可能 狀態,而開發人員可以 來觀察變化
州 | 說明 |
---|---|
進行中 |
如果網頁可供檢視且 輸入焦點 |
被動式 |
如果網頁可供檢視且確實存在,則表示網頁處於被動狀態 沒有輸入焦點
可能的舊狀態:
可能的後續狀態: |
隱藏 |
如果網頁不顯示 (且尚未顯示) 處於隱藏狀態, 可能遭到凍結、捨棄或終止)。
可能的舊狀態:
可能的後續狀態: |
凍結 |
在「凍結」狀態下,瀏覽器暫停執行
可凍結
項工作
工作佇列,直到頁面取消凍結為止。也就是說,
JavaScript 計時器和擷取回呼不會執行。已在執行
完成工作 (最重要的是
瀏覽器凍結網頁以保留 CPU/電池/數據用量;他們 也有助於加快 往返導覽功能:避免顯示完整頁面 重新載入。
可能的後續狀態: |
已終止 |
網頁開始後,就會處於「已終止」狀態 瀏覽器從記憶體卸載並清除。否 新工作可以在這個狀態下展開,而進行中的工作也可能 或是終止系統
可能的後續狀態: |
已捨棄 |
當網頁因 以節省資源不設定工作、事件回呼,或 任何類型的 JavaScript 都能在這個狀態中執行,因為通常會捨棄 發生於資源限制時,啟動新的程序時 不可能 「捨棄」狀態分頁本身 通常會向使用者顯示 (包括分頁標題與網站小圖示) 就算該網頁消失了
可能的舊狀態:
可能的後續狀態: |
活動
瀏覽器會派出大量事件,但只有一小部分會發送 頁面生命週期狀態可能有所變更。下表列出所有事件 並列出可能轉換到哪些狀態。
名稱 | 詳細資料 |
---|---|
focus
|
DOM 元素已接收焦點。
注意:
可能的舊狀態:
目前可能的狀態: |
blur
|
DOM 元素已失去焦點。
注意:
可能的舊狀態:
目前可能的狀態: |
visibilitychange
|
文件的
|
freeze
*
|
網頁剛才已凍結。不限 系統不會在頁面的工作佇列中啟動可釋放的工作。
可能的舊狀態:
目前可能的狀態: |
resume
*
|
瀏覽器已恢復凍結頁面。
可能的舊狀態:
目前可能的狀態: |
pageshow
|
正在掃遍工作階段記錄項目。 這可能是因為新的網頁載入,或是從
往返快取。如果網頁
系統是從往返快取中擷取事件
|
pagehide
|
正在掃遍工作階段記錄項目。 如果使用者正繼續前往其他網頁,而瀏覽器可以新增
目前頁面至往返
快取,以便稍後重複使用,事件的
可能的舊狀態:
目前可能的狀態: |
beforeunload
|
視窗、文件及其資源即將卸載。 文件仍會顯示,因此你仍可在此取消活動 點。
重要事項:
可能的舊狀態:
目前可能的狀態: |
unload
|
正在卸載頁面。
警告:
使用
可能的舊狀態:
目前可能的狀態: |
* 表示 Page Lifecycle API 定義的新事件
Chrome 68 新增的功能
上一張圖表顯示兩種狀態:系統啟動而非 使用者啟動:凍結和已捨棄。 如前所述,瀏覽器現在偶爾會當機並捨棄 隱藏分頁 (可自行斟酌),但開發人員無法得知 推動相關工作
在 Chrome 68 版中,開發人員現在可以觀察隱藏分頁凍結的時間
監聽freeze
和 document
上的 resume
事件。
document.addEventListener('freeze', (event) => {
// The page is now frozen.
});
document.addEventListener('resume', (event) => {
// The page has been unfrozen.
});
在 Chrome 68 版中,document
物件現在包含
wasDiscarded
屬性。(我們正針對這個問題追蹤 Android 支援情形)。判斷系統在隱藏時是否捨棄網頁
分頁中,您可以在網頁載入時檢查這個屬性的值 (注意:
捨棄的網頁必須重新載入才能使用)。
if (document.wasDiscarded) {
// Page was previously discarded by the browser while in a hidden tab.
}
有關freeze
和resume
的重要事項建議
事件,以及如何處理並準備捨棄網頁,請參閱
每個狀態的開發人員建議。
以下各節將概略介紹這些新功能如何融入 現有網路平台狀態和事件。
如何觀察程式碼中的網頁生命週期狀態
在啟用中、被動和隱藏的時間點 因此您可以執行 JavaScript 程式碼,找出目前 現有網路平台 API 的頁面生命週期狀態。
const getState = () => {
if (document.visibilityState === 'hidden') {
return 'hidden';
}
if (document.hasFocus()) {
return 'active';
}
return 'passive';
};
凍結和終止狀態,
則只能在其各自的事件接聽程式中偵測
(freeze
和 pagehide
) 為目前狀態
不斷變化
如何觀察狀態變更
以先前定義的 getState()
函式建構,可以觀察所有頁面
使用下列程式碼變更生命週期狀態。
// Stores the initial state using the `getState()` function (defined above).
let state = getState();
// Accepts a next state and, if there's been a state change, logs the
// change to the console. It also updates the `state` value defined above.
const logStateChange = (nextState) => {
const prevState = state;
if (nextState !== prevState) {
console.log(`State change: ${prevState} >>> ${nextState}`);
state = nextState;
}
};
// Options used for all event listeners.
const opts = {capture: true};
// These lifecycle events can all use the same listener to observe state
// changes (they call the `getState()` function to determine the next state).
['pageshow', 'focus', 'blur', 'visibilitychange', 'resume'].forEach((type) => {
window.addEventListener(type, () => logStateChange(getState()), opts);
});
// The next two listeners, on the other hand, can determine the next
// state from the event itself.
window.addEventListener('freeze', () => {
// In the freeze event, the next state is always frozen.
logStateChange('frozen');
}, opts);
window.addEventListener('pagehide', (event) => {
// If the event's persisted property is `true` the page is about
// to enter the back/forward cache, which is also in the frozen state.
// If the event's persisted property is not `true` the page is
// about to be unloaded.
logStateChange(event.persisted ? 'frozen' : 'terminated');
}, opts);
這段程式碼會執行以下三項作業:
- 使用
getState()
函式設定初始狀態。 - 定義可接受下一個狀態的函式,如有變更。 並記錄狀態變更
- 新增
以及
事件接聽程式
logStateChange()
傳入下一個狀態。
程式碼有一件需要注意的是
給window
,而且它們都會通過
{capture: true}
。
原因如下:
- 並非所有網頁生命週期事件的目標都相同。
pagehide
和pageshow
會在window
上觸發;visibilitychange
、freeze
和resume
發生在document
上,而focus
和blur
都會在其上觸發 個別 DOM 元素 - 這些事件大多不會以對話框形式顯示,也就是說,您無法新增 不會將事件監聽器擷取到共同祖系元素 我們很快就會深入探討 所以目前先概略介紹
- 擷取階段會在目標階段或對話框階段前執行,因此將 接聽程式,確保在其他程式碼可以取消之前執行。
各州的開發人員建議
身為開發人員,您必須瞭解頁面生命週期狀態,並且 因為您應該要... 取決於網頁狀態。
舉例來說,顯示暫時通知並不合理 使用者看到的新網頁雖然這個例子 當然,還有其他難以察覺的最佳化建議 列舉。
州 | 開發人員建議 |
---|---|
Active |
active 狀態是對使用者而言最重要的時間,因此 網頁能吸引最多時間 能快速回應使用者輸入內容 |
Passive |
在「被動」狀態中,使用者並未與網頁互動。 但仍可看見。也就是說,使用者介面更新和動畫應仍 提供流暢的體驗,但更新發生的時機並不重要。 如果網頁從「有效」變更為「被動」,表示 儲存未儲存應用程式狀態的最佳時機 |
如果頁面從「被動」變更為「隱藏」,表示 否則使用者只有在重新載入網頁後,才會再次與應用程式互動。 轉換至「隱藏」通常也會是最後狀態變更
值得開發人員信賴的例子 (尤其是在
因為使用者可以關閉分頁或瀏覽器應用程式本身,
這表示您應該將「隱藏」狀態視為 使用者的工作階段換句話說,請保留所有未儲存的應用程式狀態 傳送任何未送出的數據分析資料 您也應該停止更新 UI,因為使用者不會看見更新 ),您應該停止使用者不想執行的任何工作 在背景執行 |
|
Frozen |
處於「凍結」狀態時 「凍結」工作 工作佇列會暫停,直到頁面凍結為止,這可能會 (例如網頁遭捨棄時)。 這表示頁面會從「隱藏」變更為「凍結」 請務必停止計時器或卸除任何連線 這可能會影響其他同一個來源開啟的分頁,也可能會影響 瀏覽器可以將網頁放在 往返快取。 請特別注意以下事項:
您也應該保留所有動態檢視畫面狀態 (例如捲動位置)
)
如果頁面從「凍結」切換回「隱藏」, 您可以重新開啟任何已關閉的連線,或重新發起輪詢 網頁在最初凍結時停止。 |
Terminated |
一般而言,您不需要在頁面轉換時採取任何行動 設為 terminated 狀態 由於使用者動作會導致網頁無法載入 狀態從 hidden 狀態開始,再進入 terminated 狀態 隱藏狀態是工作階段結束邏輯 (例如 持續保留應用程式狀態和回報給數據分析) 執行任務 此外 (如同
「隱藏」狀態),開發人員必須瞭解
轉換至「終止」狀態並不可靠
多數情況下都能偵測出 (尤其是行動裝置) 所偵測的
呼叫終止事件 (例如 |
Discarded |
開發人員無法觀察「捨棄」狀態 系統就會執行這些動作這是因為網頁通常 並在資源限制下捨棄頁面,解除凍結頁面 我們無法依照 大多數情況 因此,建議您為
即可將變更從「隱藏」變更為「凍結」,然後
對捨棄的網頁載入做出相應的調整
正在檢查「 |
再次強調,因為生命週期事件的可靠性和排序並非如此 在所有瀏覽器中一致實作,讓您輕鬆遵循我們的建議 使用 PageLifecycle.js:
應避免的舊版 Lifecycle API
請盡可能避免下列事件。
卸載事件
許多開發人員會將 unload
事件視為保證回呼,並將其做為
工作階段結束信號,可儲存狀態及傳送數據分析資料,但執行這項操作
主要是不可靠,特別是在行動裝置上!unload
事件並未
在許多一般卸載情況下觸發,包括從分頁關閉分頁
或從應用程式切換器關閉瀏覽器應用程式。
因此,建議各位善用
visibilitychange
事件,用來判斷工作階段
並將隱藏狀態視為隱藏狀態
儲存應用程式和使用者資料的最可靠時間。
此外,只有已註冊的 unload
事件處理常式 (透過
onunload
或 addEventListener()
) 會
將網頁放在往返快取中,加快
返回和向前載入
建議在所有新版瀏覽器中一律使用
pagehide
事件,用於偵測可能的網頁卸載 (又稱
已終止) 狀態),而不是 unload
事件。如果發生以下情況:
需要支援 Internet Explorer 10 以下版本,
偵測 pagehide
事件,且只在瀏覽器不支援時使用 unload
pagehide
:
const terminationEvent = 'onpagehide' in self ? 'pagehide' : 'unload';
window.addEventListener(terminationEvent, (event) => {
// Note: if the browser is able to cache the page, `event.persisted`
// is `true`, and the state is frozen rather than terminated.
});
beforeunload 事件
beforeunload
事件與 unload
事件類似,在此
一直以來,如果有 beforeunload
事件,網頁
適用往返快取的資格。新版瀏覽器
則沒有這項限制雖然為了安全起見,某些瀏覽器並不會觸發
嘗試將頁面放入往返位置時,發生 beforeunload
事件
快取,也就是說,事件並不可靠做為工作階段結束信號。
此外,部分瀏覽器 (包括 Chrome)
網頁需要使用者互動,才能允許 beforeunload
事件
進而進一步影響其可靠性
beforeunload
和 unload
的差別在於:
合法使用 beforeunload
。例如,您可能想警告使用者
如果使用者繼續卸載頁面,就會失去未儲存的變更。
由於使用「beforeunload
」有正當理由,建議您
「只會」新增 beforeunload
事件監聽器,前提是使用者有未儲存的變更,
因此可以在儲存後立即移除。
換句話說,請勿這樣做 (因為此方法會新增 beforeunload
事件監聽器)
無條件):
addEventListener('beforeunload', (event) => {
// A function that returns `true` if the page has unsaved changes.
if (pageHasUnsavedChanges()) {
event.preventDefault();
// Legacy support for older browsers.
return (event.returnValue = true);
}
});
反之,因為系統只會在以下內容新增 beforeunload
事件監聽器
,如果不需要,則移除):
const beforeUnloadListener = (event) => {
event.preventDefault();
// Legacy support for older browsers.
return (event.returnValue = true);
};
// A function that invokes a callback when the page has unsaved changes.
onPageHasUnsavedChanges(() => {
addEventListener('beforeunload', beforeUnloadListener);
});
// A function that invokes a callback when the page's unsaved changes are resolved.
onAllChangesSaved(() => {
removeEventListener('beforeunload', beforeUnloadListener);
});
常見問題
為什麼沒有顯示「載入中」該怎麼辦?
Page Lifecycle API 會將狀態定義為獨立且互斥的狀態。 由於網頁可處於有效、被動或隱藏狀態 由於容器可能會在完成載入前變更狀態,甚至終止 在這個範例中並不合理。
我的網頁在隱藏時有作用,我該如何讓頁面凍結或遭到捨棄?
有許多正當理由是網頁在執行時不應凍結 都能維持隱藏狀態最顯而易見的例子,就是播放音樂的應用程式。
在某些情況下,Chrome 可能會捨棄網頁,
例如,該表單包含已取消提交的使用者輸入內容
beforeunload
處理常式會在頁面卸載時發出警告。
目前 Chrome 在捨棄網頁和 但除非不確定並不會對使用者造成影響。例如, 觀察到在隱藏狀態中採取以下任一行動 ,除非在極大的資源限制下,否則不會遭到捨棄:
- 正在播放音訊
- 使用 WebRTC
- 更新表格標題或網站小圖示
- 顯示快訊
- 傳送推播通知
瞭解目前用來判斷分頁是否可安全使用的清單功能 凍結或捨棄,請參閱:凍結和捨棄中 。
往返快取是用於描述 某些瀏覽器採用的瀏覽最佳化機制 快轉按鈕
使用者離開網頁時,這些瀏覽器會凍結
以便快速恢復運作,以備不時之需
按下「上一頁」或「下一頁」按鈕。請記住,新增 unload
事件處理常式可避免這項最佳化作業。
無論意圖和用途為何,這項凍結功能的運作方式都相同 瀏覽器停止運作以節省 CPU 或電池用量;所以總共是 視為凍結生命週期狀態的一部分。
如果無法在凍結或已終止狀態下執行非同步 API,該如何將資料儲存到 IndexedDB?
在凍結和已終止狀態中 可執行的工作 網頁的工作佇列中 暫停執行,這表示非同步和回呼型 API,例如 IndexedDB 並不能穩定地使用
日後,我們會在 IDBTransaction
物件中新增 commit()
方法,
可讓開發人員執行有效的純寫入交易
且不需回呼。也就是說,如果開發人員
資料傳送至 IndexedDB,且不執行包含讀取作業的複雜交易
commit()
方法會在工作佇列前完成
已暫停 (假設 IndexedDB 資料庫已開啟)。
不過,對於目前需要用的程式碼,開發人員有兩種做法:
- 使用工作階段儲存空間: 工作階段儲存空間 即為同步性質,且會保留在不同頁面捨棄時保留。
- 透過 Service Worker 使用 IndexedDB:服務工作處理程序可以將資料儲存在
頁面終止或捨棄後的 IndexedDB。在
freeze
或pagehide
事件監聽器,您可以透過此事件將資料傳送至 Service WorkerpostMessage()
、 而 Service Worker 可以處理儲存資料
在凍結和捨棄狀態測試應用程式
如要測試應用程式在凍結和捨棄狀態中的行為,請造訪
chrome://discards
,即可實際凍結或捨棄
開啟分頁。
這可確保你的網頁正確處理 freeze
和 resume
。
事件以及 document.wasDiscarded
旗標,這在觸發特定動作後
摘要
開發人員希望尊重使用者裝置的系統資源 建構應用程式時,應考量頁面生命週期狀態。因此,請務必 網頁未耗用過多系統資源 不會預期使用者
越多開發人員開始導入新的 Page Lifecycle API,就越安全 會導致瀏覽器凍結並捨棄未使用的網頁。這個 因此瀏覽器會耗用較少的記憶體、CPU、電池和網路資源。 這對使用者來說是雙贏