如何实现Service Worker缓存加载页面时的用户离线提示?
实现离线缓存加载的用户提示方案
方案1:修改缓存HTML响应,注入触发事件的代码
直接在Service Worker返回缓存响应前,修改HTML内容,注入触发自定义事件的脚本。核心是克隆缓存响应、转换为文本后修改,再重新构建响应返回。
self.addEventListener('fetch', (event) => { event.respondWith( // 网络优先策略:先尝试网络请求 fetch(event.request).catch(async () => { const cache = await caches.open('your-cache-name'); const cachedResponse = await cache.match(event.request); if (!cachedResponse) { return new Response('无法加载内容:当前处于离线状态', { status: 503 }); } // 仅处理HTML类型的响应,避免干扰图片、脚本等资源 const contentType = cachedResponse.headers.get('Content-Type'); if (!contentType || !contentType.includes('text/html')) { return cachedResponse; } // 克隆响应并修改HTML内容 const originalHtml = await cachedResponse.text(); // 在</body>前注入触发自定义事件的脚本 const modifiedHtml = originalHtml.replace( '</body>', '<script>window.dispatchEvent(new CustomEvent("offline-detected"));</script></body>' ); // 重新构建响应,保留原响应的状态和头部信息 return new Response(modifiedHtml, { status: cachedResponse.status, statusText: cachedResponse.statusText, headers: new Headers(cachedResponse.headers) }); }) ); });
页面侧只需监听自定义事件即可:
window.addEventListener('offline-detected', () => { // 显示离线提示横幅 const banner = document.createElement('div'); banner.style.cssText = 'position:fixed;top:0;left:0;right:0;background:#ffd166;padding:12px;text-align:center;'; banner.textContent = '当前处于离线状态,内容来自本地缓存'; document.body.appendChild(banner); });
注意:修改响应后,原响应的Content-Length头部可能失效,建议移除该头部,让浏览器自动计算长度,避免渲染异常。
方案2:Service Worker通过Client API发送离线通知
Service Worker的fetch事件中可以通过self.clients.matchAll()获取当前活跃的页面客户端,直接发送消息告知离线状态。
Service Worker端代码:
self.addEventListener('fetch', (event) => { event.respondWith( fetch(event.request).catch(async () => { const cache = await caches.open('your-cache-name'); const cachedResponse = await cache.match(event.request); if (cachedResponse) { // 获取当前所有活跃的窗口客户端 const activeClients = await self.clients.matchAll({ type: 'window', includeUncontrolled: true }); // 向每个客户端发送离线加载通知 activeClients.forEach(client => { client.postMessage({ type: 'offline-cache-load', message: '内容来自本地缓存,当前处于离线状态' }); }); } return cachedResponse || new Response('离线状态下无法加载内容', { status: 503 }); }) ); });
页面侧监听消息:
if ('serviceWorker' in navigator) { navigator.serviceWorker.addEventListener('message', (event) => { if (event.data.type === 'offline-cache-load') { // 展示提示横幅 showOfflineBanner(event.data.message); } }); // 页面加载完成后注册Service Worker(如果还没注册) navigator.serviceWorker.register('/sw.js'); } function showOfflineBanner(msg) { const banner = document.createElement('div'); banner.style.cssText = 'position:fixed;top:0;left:0;right:0;background:#ef4444;color:white;padding:12px;text-align:center;'; banner.textContent = msg; document.body.appendChild(banner); }
注意:如果页面在Service Worker发送消息前还未完成加载,可能会错过消息。可以在页面加载后主动向Service Worker发送查询请求,确认当前内容是否来自缓存。
方案3:通过自定义响应头标记缓存来源
无需修改响应体或发送消息,在Service Worker缓存响应时添加自定义头部,页面加载后通过性能API读取该头部,判断是否来自离线缓存。
Service Worker端代码:
self.addEventListener('fetch', (event) => { event.respondWith( fetch(event.request).then(async (response) => { // 网络请求成功时,缓存响应并添加标记头部 const cache = await caches.open('your-cache-name'); const cachedResponse = new Response(response.body, { status: response.status, statusText: response.statusText, headers: new Headers(response.headers) }); // 标记为网络加载的缓存 cachedResponse.headers.set('X-Cache-Status', 'online-cached'); cache.put(event.request, cachedResponse); return response; }).catch(async () => { // 离线时返回缓存,并修改头部标记 const cache = await caches.open('your-cache-name'); const cachedResponse = await cache.match(event.request); if (cachedResponse) { const offlineResponse = new Response(cachedResponse.body, { status: cachedResponse.status, statusText: cachedResponse.statusText, headers: new Headers(cachedResponse.headers) }); // 标记为离线加载的缓存 offlineResponse.headers.set('X-Cache-Status', 'offline-used'); return offlineResponse; } return new Response('无法加载内容', { status: 503 }); }) ); });
页面侧检测头部:
window.addEventListener('load', () => { const navEntry = performance.getEntriesByType('navigation')[0]; if (!navEntry) return; // 从导航性能条目里读取响应头 const cacheHeader = navEntry.responseHeaders.find(h => h.name === 'X-Cache-Status'); if (cacheHeader && cacheHeader.value === 'offline-used') { showOfflineBanner('当前离线,内容来自本地缓存'); } }); function showOfflineBanner(msg) { // 同方案2的横幅实现 const banner = document.createElement('div'); banner.style.cssText = 'position:fixed;top:0;left:0;right:0;background:#06b6d4;color:white;padding:12px;text-align:center;'; banner.textContent = msg; document.body.appendChild(banner); }
这个方案对响应内容无侵入,也不需要消息通信,兼容性较好。
内容的提问来源于stack exchange,提问作者Frédéric Camblor
相关产品推荐
相关产品推荐

