如何通过HTML实现PWA的离线访问功能?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1153个文字,预计阅读时间需要5分钟。
浏览器仅允许在安全上下文中(如HTTPS协议下或开发时的localhost)注册Service Worker。如果您的部署在HTTP域名(例如http://example.com)上,尝试使用`navigator.serviceWorker.register()`将直接失败,控制台会报错:
实操建议:
- 生产环境务必配置 HTTPS,哪怕用 Let’s Encrypt 免费证书
- 本地调试可直接用
http://localhost:3000,但不能用http://127.0.0.1:3000(部分老版本 Chrome 不认) - 确保
service-worker.js文件能被直接访问(比如访问https://yoursite.com/service-worker.js返回 200)
缓存静态资源要用 cache.addAll() 而非逐个 cache.put()
想让 PWA 离线加载 HTML/CSS/JS/图片等,最常用方式是在 install 事件里调用 cache.addAll() 预缓存关键资源。它会原子性地缓存整个列表——只要有一个文件 404 或网络失败,全部缓存就中止,避免“半残”缓存状态。
错误写法(易漏、难维护、失败不回滚):
立即学习“前端免费学习笔记(深入)”;
self.addEventListener('install', e => { e.waitUntil( caches.open('v1').then(cache => cache.put('/index.html', fetch('/index.html')) // ❌ 单个 fetch 失败不影响其他 ) ); });
正确写法:
self.addEventListener('install', e => { e.waitUntil( caches.open('v1').then(cache => cache.addAll([ '/', '/index.html', '/styles/main.css', '/scripts/app.js', '/images/logo.png' ]) ) ); });
注意:cache.addAll() 只接受相对路径(相对于 service worker 文件所在 origin),且路径必须是同源的;跨域资源(如 CDN 上的字体)需额外处理,不能直接列在这里。
离线 fallback 必须在 fetch 事件里手动拦截并返回缓存
注册和预缓存只是第一步。真正实现“断网还能打开页面”,得在 fetch 事件中拦截所有网络请求,并在失败时回退到缓存——浏览器不会自动这么做。
常见错误现象:安装了 SW、缓存了文件,但关掉网络后刷新页面仍显示 “ERR_INTERNET_DISCONNECTED”。这是因为没写 fetch 监听逻辑,或者只缓存了 JS/CSS,却没缓存 / 对应的 HTML。
基础 fallback 实现要点:
- 优先匹配缓存(
cache.match(request)),命中则直接返回 - 未命中则发起网络请求(
fetch(request)),成功后存入缓存供下次用 - 网络失败时,对 HTML 请求返回一个兜底的离线页(如
/offline.html),对其他资源(如图片)可返回空响应或占位图 - 特别注意:根路径
request.url === location.origin + '/'必须被覆盖,否则首页离线打不开
示例片段(简化版):
self.addEventListener('fetch', e => { const url = new URL(e.request.url); if (e.request.destination === 'document') { e.respondWith( caches.match(e.request).then(r => r || caches.match('/offline.html')) ); } });
skipWaiting() 和 clients.claim() 决定更新是否立即生效
用户首次访问时,SW 安装并激活,一切正常;但当你更新了 service-worker.js,新版本默认要等用户关闭所有旧页面、重新打开才会激活——这导致离线缓存长期不更新,用户实际用的还是旧资源。
解决方法是主动触发更新:
- 在新 SW 的
install事件末尾调用self.skipWaiting(),跳过 waiting 状态 - 在
activate事件里调用self.clients.claim(),让新 SW 立即接管当前页面(包括已打开的 tab) - 注意:这两步要配合使用,只写
skipWaiting()不写claim(),页面仍由旧 SW 控制
另外,如果用了缓存版本号(如 'v2'),记得在 activate 里清理旧缓存,否则磁盘越积越多:
self.addEventListener('activate', e => {
e.waitUntil(
caches.keys().then(keys =>
Promise.all(
keys.filter(k => k !== 'v2').map(k => caches.delete(k))
)
)
);
});
PWA 离线能力的关键不在“注册成功”,而在于你是否真正接管了 fetch 流程,并为每个可能的请求路径都提供了缓存策略——尤其是根路径和 HTML 页面本身,最容易被忽略。
本文共计1153个文字,预计阅读时间需要5分钟。
浏览器仅允许在安全上下文中(如HTTPS协议下或开发时的localhost)注册Service Worker。如果您的部署在HTTP域名(例如http://example.com)上,尝试使用`navigator.serviceWorker.register()`将直接失败,控制台会报错:
实操建议:
- 生产环境务必配置 HTTPS,哪怕用 Let’s Encrypt 免费证书
- 本地调试可直接用
http://localhost:3000,但不能用http://127.0.0.1:3000(部分老版本 Chrome 不认) - 确保
service-worker.js文件能被直接访问(比如访问https://yoursite.com/service-worker.js返回 200)
缓存静态资源要用 cache.addAll() 而非逐个 cache.put()
想让 PWA 离线加载 HTML/CSS/JS/图片等,最常用方式是在 install 事件里调用 cache.addAll() 预缓存关键资源。它会原子性地缓存整个列表——只要有一个文件 404 或网络失败,全部缓存就中止,避免“半残”缓存状态。
错误写法(易漏、难维护、失败不回滚):
立即学习“前端免费学习笔记(深入)”;
self.addEventListener('install', e => { e.waitUntil( caches.open('v1').then(cache => cache.put('/index.html', fetch('/index.html')) // ❌ 单个 fetch 失败不影响其他 ) ); });
正确写法:
self.addEventListener('install', e => { e.waitUntil( caches.open('v1').then(cache => cache.addAll([ '/', '/index.html', '/styles/main.css', '/scripts/app.js', '/images/logo.png' ]) ) ); });
注意:cache.addAll() 只接受相对路径(相对于 service worker 文件所在 origin),且路径必须是同源的;跨域资源(如 CDN 上的字体)需额外处理,不能直接列在这里。
离线 fallback 必须在 fetch 事件里手动拦截并返回缓存
注册和预缓存只是第一步。真正实现“断网还能打开页面”,得在 fetch 事件中拦截所有网络请求,并在失败时回退到缓存——浏览器不会自动这么做。
常见错误现象:安装了 SW、缓存了文件,但关掉网络后刷新页面仍显示 “ERR_INTERNET_DISCONNECTED”。这是因为没写 fetch 监听逻辑,或者只缓存了 JS/CSS,却没缓存 / 对应的 HTML。
基础 fallback 实现要点:
- 优先匹配缓存(
cache.match(request)),命中则直接返回 - 未命中则发起网络请求(
fetch(request)),成功后存入缓存供下次用 - 网络失败时,对 HTML 请求返回一个兜底的离线页(如
/offline.html),对其他资源(如图片)可返回空响应或占位图 - 特别注意:根路径
request.url === location.origin + '/'必须被覆盖,否则首页离线打不开
示例片段(简化版):
self.addEventListener('fetch', e => { const url = new URL(e.request.url); if (e.request.destination === 'document') { e.respondWith( caches.match(e.request).then(r => r || caches.match('/offline.html')) ); } });
skipWaiting() 和 clients.claim() 决定更新是否立即生效
用户首次访问时,SW 安装并激活,一切正常;但当你更新了 service-worker.js,新版本默认要等用户关闭所有旧页面、重新打开才会激活——这导致离线缓存长期不更新,用户实际用的还是旧资源。
解决方法是主动触发更新:
- 在新 SW 的
install事件末尾调用self.skipWaiting(),跳过 waiting 状态 - 在
activate事件里调用self.clients.claim(),让新 SW 立即接管当前页面(包括已打开的 tab) - 注意:这两步要配合使用,只写
skipWaiting()不写claim(),页面仍由旧 SW 控制
另外,如果用了缓存版本号(如 'v2'),记得在 activate 里清理旧缓存,否则磁盘越积越多:
self.addEventListener('activate', e => {
e.waitUntil(
caches.keys().then(keys =>
Promise.all(
keys.filter(k => k !== 'v2').map(k => caches.delete(k))
)
)
);
});
PWA 离线能力的关键不在“注册成功”,而在于你是否真正接管了 fetch 流程,并为每个可能的请求路径都提供了缓存策略——尤其是根路径和 HTML 页面本身,最容易被忽略。

