手機或電腦上的 Google 助理應用程式、Slack 應用程式、Zoom 應用程式,以及幾乎所有其他平台專屬應用程式,有什麼共通點?沒錯,他們至少會給你一些東西。 即使沒有網路連線,你還是可以開啟 Google 助理應用程式、輸入 Slack,或是啟動 Zoom。您可能不會獲得特別有意義的結果,甚至無法達成目標,但至少可以獲得一些東西,而且應用程式會掌控一切。



相較之下,傳統上,網頁在離線時不會提供任何內容。Chrome 提供離線恐龍遊戲,但僅此而已。


使用自訂 Service Worker 的離線備援網頁
但其實不必如此。有了服務工作人員和 Cache Storage API,您就能為使用者提供個人化的離線體驗。這可以是簡單的品牌頁面,顯示使用者目前處於離線狀態的資訊,也可以是更具創意的解決方案,例如著名的 trivago 離線迷宮遊戲,並提供手動「重新連線」按鈕和自動嘗試重新連線的倒數計時器。

註冊 Service Worker
如要達成這個目標,請使用 Service Worker。您可以從主要網頁註冊 Service Worker,如下列程式碼範例所示。通常在應用程式載入後,您會執行這項操作。
window.addEventListener("load", () => {
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("service-worker.js");
}
});
Service worker 程式碼
實際的 Service Worker 檔案內容乍看之下可能有點複雜,但下方範例中的註解應該能釐清相關概念。核心概念是預先快取名為 offline.html
的檔案,該檔案只會在失敗的導覽要求中提供,並讓瀏覽器處理所有其他情況:
/*
Copyright 2015, 2019, 2020, 2021 Google LLC. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Incrementing OFFLINE_VERSION will kick off the install event and force
// previously cached resources to be updated from the network.
// This variable is intentionally declared and unused.
// Add a comment for your linter if you want:
// eslint-disable-next-line no-unused-vars
const OFFLINE_VERSION = 1;
const CACHE_NAME = "offline";
// Customize this with a different URL if needed.
const OFFLINE_URL = "offline.html";
self.addEventListener("install", (event) => {
event.waitUntil(
(async () => {
const cache = await caches.open(CACHE_NAME);
// Setting {cache: 'reload'} in the new request ensures that the
// response isn't fulfilled from the HTTP cache; i.e., it will be
// from the network.
await cache.add(new Request(OFFLINE_URL, { cache: "reload" }));
})()
);
// Force the waiting service worker to become the active service worker.
self.skipWaiting();
});
self.addEventListener("activate", (event) => {
event.waitUntil(
(async () => {
// Enable navigation preload if it's supported.
// See https://developers.google.com/web/updates/2017/02/navigation-preload
if ("navigationPreload" in self.registration) {
await self.registration.navigationPreload.enable();
}
})()
);
// Tell the active service worker to take control of the page immediately.
self.clients.claim();
});
self.addEventListener("fetch", (event) => {
// Only call event.respondWith() if this is a navigation request
// for an HTML page.
if (event.request.mode === "navigate") {
event.respondWith(
(async () => {
try {
// First, try to use the navigation preload response if it's
// supported.
const preloadResponse = await event.preloadResponse;
if (preloadResponse) {
return preloadResponse;
}
// Always try the network first.
const networkResponse = await fetch(event.request);
return networkResponse;
} catch (error) {
// catch is only triggered if an exception is thrown, which is
// likely due to a network error.
// If fetch() returns a valid HTTP response with a response code in
// the 4xx or 5xx range, the catch() will NOT be called.
console.log("Fetch failed; returning offline page instead.", error);
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(OFFLINE_URL);
return cachedResponse;
}
})()
);
}
// If our if() condition is false, then this fetch handler won't
// intercept the request. If there are any other fetch handlers
// registered, they will get a chance to call event.respondWith().
// If no fetch handlers call event.respondWith(), the request
// will be handled by the browser as if there were no service
// worker involvement.
});
離線備援網頁
您可以在 offline.html
檔案中發揮創意,根據需求調整檔案,並加入品牌宣傳。以下範例顯示最基本的功能。
這個範例會示範如何根據按鈕按下動作手動重新載入,以及如何根據 online
事件和定期伺服器輪詢自動重新載入。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>You are offline</title>
<!-- Inline the page's stylesheet. -->
<style>
body {
font-family: helvetica, arial, sans-serif;
margin: 2em;
}
h1 {
font-style: italic;
color: #373fff;
}
p {
margin-block: 1rem;
}
button {
display: block;
}
</style>
</head>
<body>
<h1>You are offline</h1>
<p>Click the button below to try reloading.</p>
<button type="button">⤾ Reload</button>
<!-- Inline the page's JavaScript file. -->
<script>
// Manual reload feature.
document.querySelector("button").addEventListener("click", () => {
window.location.reload();
});
// Listen to changes in the network state, reload when online.
// This handles the case when the device is completely offline.
window.addEventListener('online', () => {
window.location.reload();
});
// Check if the server is responding and reload the page if it is.
// This handles the case when the device is online, but the server
// is offline or misbehaving.
async function checkNetworkAndReload() {
try {
const response = await fetch('.');
// Verify we get a valid response from the server
if (response.status >= 200 && response.status < 500) {
window.location.reload();
return;
}
} catch {
// Unable to connect to the server, ignore.
}
window.setTimeout(checkNetworkAndReload, 2500);
}
checkNetworkAndReload();
</script>
</body>
</html>
示範
您可以在下方嵌入的示範中,查看離線備用網頁的實際運作情形。如有興趣,可以探索 GitHub 上的原始碼。
關於讓應用程式可安裝的附註
現在網站已有離線備援網頁,您可能會想知道接下來該怎麼做。如要讓使用者安裝應用程式,請新增網頁應用程式資訊清單,並視需要制定安裝策略。
附註:使用 Workbox.js 提供離線備用網頁
您可能聽過 Workbox。 Workbox 是一組 JavaScript 程式庫,可為網頁應用程式新增離線支援功能。如果您不想自行編寫太多 Service Worker 程式碼,可以使用 Workbox 食譜,只建立離線網頁。
接下來,請瞭解如何為應用程式定義安裝策略。