visibilitychange事件必须在DOM解析早期注册,否则冷启动即失效;页面hidden时应暂停轮询、动画、音视频及非关键请求,恢复时需防重复与平台兼容性问题。

visibilitychange 事件必须在 DOM 解析早期注册,否则冷启动就失效
页面首次加载时,document.hidden 可能已是 true(比如 Chrome 预加载、iOS Safari 恢复后台页),但 visibilitychange 不会“回补”已发生的状态变化。监听加晚了,等于没监听。
- 直接把
document.addEventListener('visibilitychange', ...)写在<script>标签最顶部,不依赖任何框架钩子或DOMContentLoaded - 避免包裹在
setTimeout、动态import()或 Vue/React 的mounted里 - 若用构建工具(如 Vite/Webpack),确保该脚本
type="module"或带defer属性,且不被 code-splitting 拆走
页面 hidden 时该停什么,不该停什么
浏览器不会自动停掉任何 JS 逻辑,所有资源释放都得你手动做。重点不是“全停”,而是停那些用户看不见却持续消耗 CPU/GPU/网络的活儿。
-
setInterval/setTimeout轮询(如每 3sfetch新消息)→ 必须clearInterval(id) -
requestAnimationFrame动画循环 → 记下返回的rafId,hidden 时调cancelAnimationFrame(rafId) - 视频/音频播放 →
video.pause(),但别重置currentTime,保留进度 - Canvas/WebGL 渲染 → 停掉
render()调用,GPU 占用立刻下降 - 非关键日志上报 → 暂存到数组,visible 后批量发,避免小包堆积
-
不该停:WebSocket 连接本身(息屏后 TCP 可能还挂着)、
localStorage读写、纯计算逻辑
visible 恢复时重启动要防重复和失败
切回前台 ≠ 立刻能干活。尤其 iOS Safari 和微信 WebView 对媒体播放、定时器恢复极其敏感,盲目重启大概率报错或卡死。
- 恢复播放前先检查
video.readyState >= 4,再video.play().catch(...) - 动画重启别用旧
rafId,重新调requestAnimationFrame(loop) - 轮询重启前确认
myTimer是否为null或已清除,避免setInterval多开 - iOS 上
video.play()必须延迟:用setTimeout(..., 2000),200ms 在部分机型上不够稳 - 如果用了 WebSocket,visible 时别急着重连——先看
ws.readyState,=== 0或=== 3才触发重连逻辑
移动端息屏场景必须兜底 pagehide + 特殊平台事件
visibilitychange 在 Android WebView、微信、手 Q 等环境不可靠:锁屏不触发、切 App 不触发、甚至始终返回 visible。单靠它,移动端基本废一半。
立即学习“前端免费学习笔记(深入)”;
- 必须搭配
pagehide事件:它在标签页卸载、刷新、关闭前必触发,比visibilitychange更可靠 - 微信内嵌页支持标准
visibilitychange,但老版 UIWebView(已基本淘汰)不支持 → 可忽略 - 手 Q 不触发
visibilitychange,要用qbrowserVisibilityChange:直接document.addEventListener('qbrowserVisibilityChange', ...) - Android WebView 若始终不触发
hidden,可 fallback 到监听blur+focus(仅限 PC 兼容,移动端意义有限)
真正难的不是写几行监听代码,而是判断哪些逻辑该停、停多久、恢复时要不要等用户手势、以及不同平台间状态信号的交叉验证。一个没处理好的 video.play() 就能让整个页面白屏,而一个漏掉的 clearInterval 会在后台悄悄吃掉 30% 内存。



















