真实离线检测需综合html结构、js事件与fetch探测:用data-status存标准化状态值,online/offline事件必须绑定window且早注册,fetch探活/health接口验证业务连通性,service worker缓存需带版本号并克隆响应体。

不能只靠 navigator.onLine 判断用户是否真能访问你的服务——它返回的是操作系统接口状态,不是业务连通性。真实可用的离线提醒,必须把 HTML 结构、JS 事件、探测逻辑和降级策略拧在一起做。
怎么写 HTML 结构才不拖累 JS 逻辑
结构要为状态变化留出明确出口,而不是让 JS 去“猜”或“挖”文本:
- 用
data-status存标准化值,比如<div data-status="offline"></div>,别写<div class="offline-tip">已离线</div> - 避免在
title、alt或纯文本节点里藏状态——屏幕阅读器会读,SEO 会索引,JS 却难提取、难更新 - 关键提示区域加
id或data-offline-target,方便 JS 快速定位并更新,例如<div data-offline-target="" aria-live="polite"></div> - 如果后端能渲染,就让服务端把初始状态(如
data-status="online")和文案(如data-display-text="网络正常")一起塞进 DOM,JS 只负责后续刷新
online/offline 事件监听必须绑定在 window 上
这两个事件不会冒泡到 document 或 body,也不支持事件委托。绑错位置等于没绑:
- 必须用
window.addEventListener('online', handler),不能写成document.addEventListener('online', handler) - 注册时机要早:放在
<script></script>标签最开头,或DOMContentLoaded回调里,别等 React/Vue mount 完再加 - 别用
window.ononline = handler——它会覆盖其他模块注册的监听器,尤其在微前端或多 SDK 场景下极易出问题 - 记得清理:在单页应用路由切换或组件卸载时,调用
removeEventListener,否则可能触发多次重复回调
为什么 fetch 探测比 navigator.onLine 更可信
navigator.onLine 在 WiFi 连着但路由器断网、公司内网通但外网被墙、安卓 WebView 长期卡死等场景下全都会返回 true,而你的真实 API 已经 404 了。
- 每次
online事件触发后,立刻发一次轻量fetch('/health', { method: 'HEAD', cache: 'no-store', signal: AbortSignal.timeout(1500) }) - 只接受
2xx状态码;超时、0状态码、4xx/5xx都算“不可达”,维持离线 UI -
/health接口必须无依赖:不读 session、不校验 token、不查 DB,返回空 body +200即可 - 不要每秒轮询——只在状态切换后探一次,或用户点击“重试”时再探,否则服务端限流、移动端耗电都找你
Service Worker 缓存 offline.html 的几个硬坑
缓存离线页不是 copy-paste 就完事,路径、克隆、版本三处写错,离线时页面就白屏或卡死:
-
cacheName必须带版本号,比如'v2-offline-cache',改了缓存逻辑就得换名字,否则旧 SW 还在读旧 cache - 注册后立即执行
registration.waiting.postMessage('skipWaiting'),不然新 SW 卡在 waiting 状态,离线时仍用老逻辑 - 缓存 HTML 时必须用
response.clone():cache.put('/', response.clone()),否则response.body被读一次就空了,后续caches.match()拿不到内容 - 别缓存带查询参数的 URL(如
/index.html?v=1.2.3),它们无法命中 cache;静态资源用哈希命名,HTML 用根路径/缓存
真正麻烦的从来不是“怎么显示离线提示”,而是“怎么定义‘离线’”。时间偏移、标签页休眠、WebView 行为差异、多网卡干扰……这些细节堆起来,才是线上用户看到的“有时准、有时不准”的根源。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











