visibilitychange 事件必须绑定到 document 而非 window,且不冒泡;判断显隐需用 document.hidden 而非 event.type;iOS 存在触发延迟或漏发问题;SPA 中需避免重复绑定和内存泄漏。
很多人在写 visibilitychange 时习惯性绑定到 window,但这个事件只在 document 上触发。绑错对象会导致监听完全失效,且无任何报错提示。
document.addEventListener('visibilitychange', handler) ✅ 正确window.addEventListener('visibilitychange', handler) ❌ 不会触发事件名是 visibilitychange,但它的触发本身不带“当前是否可见”的状态信息。真正反映页面显隐状态的是 document.hidden 布尔值——true 表示页面被隐藏(如切到其他 tab、最小化浏览器),false 表示可见。
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
console.log('页面已隐藏');
} else {
console.log('页面已显示');
}
});
iOS 13+ Safari 和部分 WKWebView 环境中,document.hidden 在页面刚加载时可能为 false,但实际尚未渲染完成;更关键的是:当用户从后台切回 App 时,visibilitychange 有时延迟触发或漏发。
document.hidden 值做初始化逻辑pagehide/pageshow 事件在单页应用(SPA)中,如果组件反复挂载/卸载(比如 React useEffect 或 Vue onMounted),容易多次绑定同一事件却未清理,导致 handler 被执行多次。
removeEventListener 清理旧监听(需保存 handler 引用)
document.addEventListener('visibilitychange', () => {...}) → 无法解绑const handleVisibilityChange = () => {
// ...
};
// 绑定
document.addEventListener('visibilitychange', handleVisibilityChange);
// 解绑(例如在组件 unmount 时)
document.removeEventListener('visibilitychange', handleVisibilityChange);
页面显隐状态看似简单,但 document.hidden 的初始值、iOS 平台的事件时机、以及 SPA 中的监听生命周期,三者叠加最容易出隐蔽问题。