一、什么是 visibilitychange 事件?
visibilitychange 是浏览器提供的原生事件,属于 Page Visibility API(页面可见性 API)的核心部分。当页面的可见性状态发生变化时(如页面从 "显示" 变为 "隐藏",或从 "隐藏" 变为 "显示"),浏览器会触发该事件,当然如果是uniapp或者是wx也会提供类似apphide appshow这些API。
- 核心作用:判断页面是否处于用户可见状态(前台)或不可见状态(后台,如切换到其他 App、浏览器标签页被切换、手机锁屏等)。
二、核心属性与事件
1. document.hidden(状态判断核心)
- 布尔值,
true表示页面当前不可见(后台),false表示页面可见(前台)。 - 是判断页面状态的直接依据。
2. document.visibilityState(更详细的状态描述)
-
字符串类型,返回页面的具体可见性状态,有 4 种可能值:
visible:页面可见(至少部分可见,如浏览器窗口未最小化,标签页激活)。hidden:页面不可见(如切换到其他标签页、浏览器最小化、手机切后台)。prerender:页面正在预渲染(用户未实际看到,通常用于优化加载,部分浏览器支持)。unloaded:页面即将被卸载(如关闭标签页,部分浏览器支持)。
-
实际开发中,
hidden和visible是最常用的两个状态。
3. visibilitychange 事件
- 当
document.visibilityState或document.hidden变化时触发,需通过document.addEventListener监听。
三、基本用法(完整代码示例)
1. 基础监听逻辑
javascript
// 监听 visibilitychange 事件
document.addEventListener('visibilitychange', handleVisibilityChange);
// 事件处理函数
function handleVisibilityChange() {
// 方法1:用 document.hidden 判断
if (document.hidden) {
console.log('页面进入后台(不可见)');
// 执行后台逻辑:暂停视频、清除定时器、保存数据等
pauseVideo();
clearInterval(timer);
saveUserState();
} else {
console.log('页面回到前台(可见)');
// 执行前台逻辑:恢复视频播放、重启定时器、刷新数据等
playVideo();
restartTimer();
refreshData();
}
// 方法2:用 document.visibilityState 判断(更细致)
switch (document.visibilityState) {
case 'visible':
console.log('页面可见(前台)');
break;
case 'hidden':
console.log('页面隐藏(后台)');
break;
case 'prerender':
console.log('页面预渲染中');
break;
case 'unloaded':
console.log('页面即将卸载');
break;
}
}
2. 解除监听(避免内存泄漏)
当页面不需要监听状态变化时(如组件销毁),需移除事件监听:
javascript
// 页面卸载前解除监听
window.addEventListener('beforeunload', function() {
document.removeEventListener('visibilitychange', handleVisibilityChange);
});
// 或在 Vue/React 组件销毁时(以 Vue 为例)
export default {
beforeDestroy() {
document.removeEventListener('visibilitychange', this.handleVisibilityChange);
}
};
四、适用场景
1. 媒体播放控制
-
页面切后台时暂停视频 / 音频,切回前台时恢复播放:
javascriptconst video = document.getElementById('myVideo'); function handleVisibilityChange() { if (document.hidden) { video.pause(); // 切后台暂停 } else { video.play(); // 切前台恢复 } }
2. 减少无效请求
-
页面在后台时,暂停轮询接口(如实时聊天、数据刷新),避免浪费资源:
scsslet pollTimer; function startPoll() { pollTimer = setInterval(() => { fetch('/api/refresh'); // 轮询接口 }, 5000); } function stopPoll() { clearInterval(pollTimer); } function handleVisibilityChange() { if (document.hidden) { stopPoll(); // 后台停止轮询 } else { startPoll(); // 前台重启轮询 } }
3. 记录用户在线状态
-
切后台时记录 "离线时间",切前台时更新 "在线状态":
javascriptlet lastLeaveTime; function handleVisibilityChange() { if (document.hidden) { lastLeaveTime = new Date().getTime(); // 记录切后台时间 reportUserState('offline'); // 上报离线状态 } else { const onlineTime = new Date().getTime() - lastLeaveTime; console.log(`用户离线时长:${onlineTime}ms`); reportUserState('online'); // 上报在线状态 } }
4. 防止表单数据丢失
-
切后台时自动保存表单草稿,避免用户忘记提交:
javascriptfunction saveFormDraft() { const formData = { username: document.getElementById('username').value, content: document.getElementById('content').value }; localStorage.setItem('formDraft', JSON.stringify(formData)); } function handleVisibilityChange() { if (document.hidden) { saveFormDraft(); // 切后台时保存草稿 } }
五、兼容性与浏览器支持
1. 主流浏览器支持情况
- PC 端:Chrome、Firefox、Edge、Safari(≥6.1)全支持。
- 移动端:微信内置浏览器、手机 Chrome、Safari(iOS ≥7.1)、安卓系统浏览器(≥4.4)全支持。
- 低版本兼容 :IE10+ 支持,但需用前缀
msVisibilityChange和msHidden(实际开发中可忽略,IE 市场占比极低)。
六、注意
1. 与 pagehide/pageshow 的区别
visibilitychange:仅关注页面 "可见性"(是否在前台),不关心页面是否卸载(如切换标签页时触发,但页面未关闭。pagehide:页面即将被卸载时触发(如关闭标签页、跳转页面),但切后台时也可能触发,场景更宽泛,判断精度低于visibilitychange。- 结论 :判断 "切后台" 优先用
visibilitychange,判断 "页面关闭" 可用pagehide。
2. 微信 / 企业微信环境的特殊性
- 微信内置浏览器完全支持
visibilitychange,无需依赖微信 JS-SDK,可直接使用。 - 但需注意:微信中 "分享到朋友圈 / 好友" 时,页面会短暂切后台再切回,可能触发一次
visibilitychange(从visible→hidden→visible),需根据业务判断是否忽略这种场景。
3. 避免过度使用
- 频繁触发的逻辑(如大量 DOM 操作)放在
visibilitychange中可能影响性能,建议仅处理必要操作(如暂停 / 恢复、保存数据)。
4. 锁屏状态的触发
- 手机锁屏时,页面会被视为 "隐藏",触发
visibilitychange(document.hidden = true);解锁后恢复,触发visible。
visibilitychange 事件是 H5 页面监听 "前台 / 后台切换" 的最优方案,具有以下优势:
- 原生支持:无需依赖任何 SDK(包括微信 / 企业微信),兼容性极佳。
- 精准可靠:能覆盖 "切 App、切换标签页、锁屏" 等所有页面可见性变化场景。
- 用法简单 :通过
document.hidden或visibilityState即可判断状态,代码逻辑清晰。
缺点:无法区分隐藏原因
是的,你没看错,先前讲到的只是常规情况下用户手动切换后台的触发, 但是在一些场景下还是要慎用
1. 无法区分 "切后台" 的具体原因
visibilitychange 只能判断页面 "可见" 或 "隐藏",但无法区分隐藏的具体场景,例如:
- 用户是 "切换到其他 App" 还是 "切换到浏览器的其他标签页"?
- 是 "手机锁屏" 还是 "浏览器窗口最小化"?
- 是 "分享到微信好友后暂时离开" 还是 "彻底关闭页面"?
2. 部分浏览器 / 场景下触发时机不精准
- 微信 / 支付宝等 App 内置浏览器 :在某些操作(如点击分享、拉起原生组件)时,可能会出现 "短暂隐藏再恢复" 的误触发。例如,在微信中点击 "分享到朋友圈",页面会先切后台(触发
hidden),分享完成后切回前台(触发visible),但用户实际并未离开页面,可能干扰业务逻辑(如暂停的视频被误触发播放 / 暂停)。 - iOS 端的特殊行为 :在 iOS Safari 中,当页面处于后台且内存不足时,浏览器可能会冻结页面 JS 执行,导致
visibilitychange事件在页面恢复时延迟触发,或部分逻辑(如定时器)无法正常执行。
3. 无法监听 "页面关闭" 的最终状态
visibilitychange 的 unloaded 状态在多数浏览器中支持不佳,且页面真正关闭时(如用户点击关闭标签页),visibilitychange 可能与 beforeunload、pagehide 事件顺序混乱,难以可靠判断 "用户是否彻底离开"。例如,用户关闭标签页时,hidden 会先变为 true,但此时页面即将卸载,后续逻辑(如上报数据)可能因页面关闭而中断。
4. 对 "部分可见" 状态的判断有限
document.visibilityState = 'visible' 表示页面 "至少部分可见"(如浏览器窗口只露出一小块),但无法判断页面是否 "完全可见"(如被其他窗口遮挡了大部分)。如果业务需要精确判断 "用户是否正在全屏浏览",visibilitychange 无法满足,需结合 document.fullscreenElement 等 API 辅助判断。
用户是 "切换到其他 App" 还是 "切换到浏览器的其他标签页"?
是 "手机锁屏" 还是 "浏览器窗口最小化"
是 "分享到微信好友后暂时离开" 还是 "彻底关闭页面"?
上面三种是常见的场景,通常可以这样的辅助方法进行判断
一、区分 "切换到其他 App" vs "切换到浏览器其他标签页"
核心思路:利用浏览器标签页的 "焦点状态" 和 "页面可见性" 的关联性
- 切换到浏览器其他标签页 时:页面失去焦点(
window.blur)且可见性变为隐藏(document.hidden = true),但浏览器进程仍在运行,setTimeout等定时器可能继续执行(取决于浏览器优化策略)。 - 切换到其他 App 时:页面不仅隐藏(
document.hidden = true),还可能伴随浏览器进程被 "冻结"(尤其是移动端),定时器执行会延迟或暂停。
辅助判断方法:
-
结合
focus/blur事件与定时器延迟检测(一般情况可用,所以慎用):javascriptlet isTabSwitch = false; let timer; // 监听可见性变化 document.addEventListener('visibilitychange', () => { if (document.hidden) { // 页面隐藏时启动定时器,检测延迟 timer = setTimeout(() => { // 若定时器延迟超过100ms,大概率是切换到其他App(浏览器被冻结) console.log('可能切换到其他App'); }, 100); } else { clearTimeout(timer); // 页面恢复时,若之前触发了blur且无明显延迟,可能是切换标签页 if (isTabSwitch) { console.log('可能切换到浏览器其他标签页'); isTabSwitch = false; } } }); // 监听焦点变化 window.addEventListener('blur', () => { if (document.hidden) { isTabSwitch = true; // 隐藏时失去焦点,可能是切换标签页 } });- 原理:切换标签页时,浏览器仍在前台运行,定时器延迟较小;切换到其他 App 时,浏览器进入后台,定时器可能被延迟执行。
二、区分 "手机锁屏" vs "浏览器窗口最小化"
核心思路:利用 "锁屏" 的特殊性 ------ 通常伴随设备屏幕关闭,而 "窗口最小化" 仅窗口不可见
- 手机锁屏时:屏幕完全关闭,浏览器可能触发
visibilitychange且后续操作(如触摸事件)完全失效。 - 浏览器窗口最小化(PC 端):屏幕仍亮,只是窗口不可见。
辅助判断方法:
-
结合
screen对象的亮度或唤醒状态(移动端有限支持):javascriptdocument.addEventListener('visibilitychange', () => { if (document.hidden) { // 检测屏幕是否变暗(部分设备支持) if (typeof screen.brightness !== 'undefined' && screen.brightness < 0.1) { console.log('可能是手机锁屏'); } else { console.log('可能是浏览器窗口最小化'); } } });- 注意:
screen.brightness兼容性较差(主要支持安卓部分浏览器),iOS 基本不支持。
- 注意:
-
监听
touchstart事件是否失效(移动端):锁屏后,页面无法接收触摸事件,可在页面恢复可见时检测是否有 "锁屏期间的触摸记录"(无记录则可能是锁屏):javascriptlet hasTouchDuringHidden = false; document.addEventListener('touchstart', () => { if (document.hidden) { hasTouchDuringHidden = true; } }); document.addEventListener('visibilitychange', () => { if (!document.hidden) { if (!hasTouchDuringHidden) { console.log('可能是手机锁屏(期间无触摸)'); } else { console.log('可能是窗口最小化(期间可能有触摸其他窗口)'); hasTouchDuringHidden = false; } } });
三、区分 "分享到微信好友后暂时离开" vs "彻底关闭页面"
核心思路:利用 "分享" 操作的前置行为(如点击分享按钮)和页面生命周期差异
- 分享到微信好友后暂时离开:用户点击分享按钮 → 页面隐藏 → 分享完成后用户可能返回,页面会再次触发
visible。 - 彻底关闭页面:页面隐藏后,会接着触发
pagehide或beforeunload事件,且不会再恢复visible。
辅助判断方法:
-
监听微信分享按钮的点击事件(微信 H5 场景):在微信环境中,用户分享前通常会点击自定义的 "分享按钮",可通过该行为标记 "可能是分享导致的离开":
javascriptlet isSharing = false; // 假设分享按钮id为shareBtn document.getElementById('shareBtn').addEventListener('click', () => { isSharing = true; // 标记用户触发了分享操作 }); document.addEventListener('visibilitychange', () => { if (document.hidden) { if (isSharing) { console.log('可能是分享到微信好友后暂时离开'); // 30秒后若未恢复可见,视为可能关闭页面 setTimeout(() => { if (document.hidden) { console.log('分享后未返回,可能已关闭页面'); isSharing = false; } }, 30000); } else { console.log('可能是其他原因关闭页面'); } } else { if (isSharing) { console.log('分享后返回页面'); isSharing = false; } } }); -
结合
pagehide事件判断页面是否卸载 :彻底关闭页面时,pagehide事件会在visibilitychange之后触发,可通过该事件确认 "页面已关闭":javascriptlet isPageClosed = false; window.addEventListener('pagehide', () => { isPageClosed = true; console.log('页面已关闭'); }); document.addEventListener('visibilitychange', () => { if (document.hidden && !isPageClosed) { console.log('页面隐藏但未关闭(可能是分享后暂时离开)'); } });总结:
没有绝对的完美,只有不断地完善,当后台切换时还是要尽可能的与原有应用进行事件关联才会更准确的判断用户的操作行为。