navigator.onLine 不可靠,仅反映系统网络接口状态而非服务可达性,需配合 fetch 心跳检测(带超时、防抖、真实后端校验)和服务端预缓存离线页才能准确判断用户可用性。

单独用 navigator.onLine 显示“无网络提示”基本不可用——它在路由器断网、DNS 失效、代理异常时仍返回 true,但用户实际已无法访问你的 API。
为什么 navigator.onLine 不能信
这个属性只反映操作系统网络接口是否启用,不是服务可达性判断。比如 WiFi 图标亮着,但光猫掉线、防火墙拦截了你的域名、CDN 返回 503,navigator.onLine 依然为 true。它只在你手动关 WiFi 或开飞行模式时才准确变 false。
- 安卓 WebView 默认不触发
offline事件,iOS WKWebView 有已知 bug,完全不派发 - 事件延迟常见:从真实断网到触发
offline可能长达 2–5 秒,甚至不触发 - 它无法区分“连得上但后端挂了”和“完全没网”,而用户真正关心的是“我的页面还能不能用”
必须搭配 fetch 心跳检测才靠谱
前端要确认的从来不是“有没有 IP 连接”,而是“能不能拿到 /api/ping 的 200 响应”。心跳必须带超时、防抖、且 endpoint 由后端真实校验依赖(如 DB 连通性),不能只是返回静态 200。
- 用
AbortController控制超时,例如setTimeout(() => controller.abort(), 3000),避免 fetch 卡死 - 不要请求
/、/favicon.ico或 CDN 资源——它们可能被缓存或绕过服务端,无法反映真实状态 - 心跳间隔建议 ≥15 秒;太频繁会触发 CORS 预检、耗电、被服务端限流
- 收到
online事件后,别立刻隐藏提示,先发一次心跳,成功再更新 UI 状态
Service Worker 离线页必须预缓存且自包含
想让用户断网后点刷新还能看到提示页,核心不是 JS 判断,而是 Service Worker 在 install 阶段就把 /offline.html 写进 cache,并在 fetch 事件中对导航请求(request.mode === 'navigate')直接返回它。
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
立即学习“前端免费学习笔记(深入)”;
- 缓存路径必须严格一致:install 里
cache.addAll(['/offline.html']),fetch 里就只能用caches.match('/offline.html') -
/offline.html必须内联所有 CSS/JS,不能引用任何外部资源(包括 Google Fonts、CDN jQuery),否则自己加载失败就白屏 - 返回响应要用
new Response(body, { status: 503, headers: { 'Content-Type': 'text/html' } }),不能用Response.redirect()(离线时重定向会失败) - 注册 SW 后调用
registration.waiting.postMessage('skipWaiting'),否则新版本卡在 waiting 状态,断网时仍走旧逻辑
提示文案和动画容易被忽略的细节
网络抖动时,online/offline 事件 + 心跳失败可能密集触发,导致“重新连接中…”反复闪现,体验极差。
- 加 1.5 秒防抖:收到离线信号不立即显示,等 1.5 秒没恢复再 show;收到 online 后也不立即 hide,先心跳成功再收起
- 用 CSS
opacity+pointer-events: none控制显隐,别反复增删 DOM 元素,避免重排 - 文案统一用“重新连接中…”,不要写“第 2 次尝试…”——干扰信息毫无意义,还增加 DOM 更新负担
- 记录上次状态变更时间戳,若两次离线间隔
最常翻车的点不在逻辑多复杂,而在缓存路径少个斜杠、offline.html 里多引了一个 CDN 字体、或者忘了 response.clone() 导致缓存写入空 body——这些都会让离线页在关键时刻彻底消失。


















