visibilitychange 事件需在 DOM 解析早期注册,否则冷启动或后台恢复时会错过初始状态;恢复播放须延迟并检查 readyState;有声视频必须 muted 才能 autoplay;还需统一管理 rAF、AudioContext 等资源。

visibilitychange 事件注册时机不对,监听就失效了
页面切后台时 visibilitychange 没触发,大概率不是 API 不工作,而是监听加晚了。比如把 addEventListener('visibilitychange', ...) 放在 DOMContentLoaded 之后、或包裹在 setTimeout / import() 动态加载逻辑里,就可能错过初始状态——尤其是 PWA 冷启动、iOS 微信从后台恢复时,document.hidden 一开始就是 true,但事件不会“回补”。
必须在 DOM 解析阶段尽早注册:
- 直接写在
<script>标签最顶部(不依赖任何其他脚本) - 不要等
window.onload或框架的mounted钩子 - 避免用模块懒加载方式注册该监听器
只判断 document.hidden 就暂停,iOS 上 resume 失败很常见
iOS Safari 和微信 WebView 对 play() 调用极其敏感:页面恢复后立刻调用 video.play() 几乎必报错 DOMException: play() failed because the user didn't interact with the document first。这不是 bug,是策略限制——即使用户刚切回来,浏览器仍视其为“无用户交互上下文”。
实操要点:
立即学习“前端免费学习笔记(深入)”;
- 恢复播放必须加
setTimeout(..., 200–2000)延迟,iOS 上 2000ms 更稳妥 - 优先检查
video.readyState >= 4(即已加载元数据且可播放),再调play() - 捕获
play()的 Promise reject,失败时记录日志,不抛错阻断逻辑
示例片段:
document.addEventListener('visibilitychange', () => {
const video = document.getElementById('myVideo');
if (document.hidden) {
video.pause();
} else {
setTimeout(() => {
if (video.readyState >= 4) {
video.play().catch(e => console.warn('resume play failed:', e));
}
}, 2000);
}
});
没处理 audio/video 的 muted 状态,autoplay 一开始就卡死
现代浏览器(Chrome 117+、Safari 17、Firefox)默认禁止有声媒体自动播放。如果你的视频带音频轨道,又没加 muted 属性,即使写了 autoplay,页面首次加载也会静音 + 暂停,控制台报错,后续 play() 调用也大概率失败。
必须同时满足:
-
<video autoplay muted loop playsinline>—— 四个属性缺一不可(playsinline尤其对 iOS 必需) - 如果业务必须有声,首次播放必须由用户点击等交互触发(如“点击开始”按钮),之后再用
visibilitychange控制续播 - 切后台前若已解除
muted,恢复时要重新设回muted=false并重试play()
只停 video.pause(),漏掉 requestAnimationFrame 和 AudioContext
很多人以为暂停 <video> 就万事大吉,其实后台仍在偷偷耗电:
-
requestAnimationFrame在 iOS Safari 中**不会自动节流**,切后台后持续执行,CPU 占用高、发热快 -
AudioContext(Web Audio)在页面隐藏时被系统 suspend,恢复后必须显式调用audioContext.resume(),否则音频静音 - 某些轮播图、粒子动画、实时图表依赖
setInterval或setTimeout,也要统一清理
正确做法是:所有周期性任务 ID(rafId, timerId, audioContext)都存到闭包或模块级变量里,visibilitychange 触发时统一销毁/恢复。
容易被忽略的一点:document.visibilityState === 'visible' 才能安全恢复,不能只靠 !document.hidden —— 因为 visibilityState 还可能为 'prerender' 或 'unloaded'(虽然后者极少见)。



















