You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 20:25:14