在前端开发中,“用户离开当前页面” 包含多种场景:切换标签页、最小化浏览器、关闭标签页 / 浏览器、跳转到其他页面、电脑休眠 / 锁屏等。针对不同场景,我们可以通过浏览器提供的 API 进行精准检测,以下是详细的实现方案和场景适配。
一、核心 API 分类及适用场景
1. Page Visibility API(页面可见性检测)
这是最常用、最精准的检测方案,专门用于判断页面是否处于 “可见状态”,能区分「切换标签页、最小化浏览器、锁屏」等场景。
核心原理
浏览器提供 布尔值(
document.hidden 表示页面不可见,
true 表示可见),并通过
false 事件触发状态变更。
visibilitychange
代码实现
// 监听页面可见性变化
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
// 页面变为不可见(切换标签、最小化、锁屏等)
console.log('用户离开了当前页面(不可见)');
// 执行业务逻辑:暂停视频播放、暂停定时器、上报用户离开行为等
handlePageLeave();
} else {
// 页面恢复可见(用户切回标签)
console.log('用户回到了当前页面');
handlePageResume();
}
});
// 离开页面的处理逻辑
function handlePageLeave() {
// 示例:暂停视频
const video = document.querySelector('video');
if (video) video.pause();
// 示例:上报用户离开行为
fetch('/api/user/leave', { method: 'POST' });
}
// 回到页面的处理逻辑
function handlePageResume() {
// 示例:恢复视频播放
const video = document.querySelector('video');
if (video) video.play();
}
关键说明
触发场景:✅ 切换到其他标签页✅ 最小化浏览器窗口✅ 电脑锁屏 / 休眠✅ 切换到浏览器的其他窗口❌ 关闭标签页 / 浏览器(部分浏览器可能触发,但不可靠)❌ 跳转到其他页面(不会触发,因为页面已卸载)兼容性:支持 IE10+、所有现代浏览器(Chrome、Firefox、Safari 等),是首选方案。扩展属性:
:返回更详细的状态,可选值:
document.visibilityState
:页面可见(前台标签)
visible:页面不可见(后台标签、最小化、锁屏)
hidden:页面预渲染(不可见)
prerender:页面即将卸载(部分浏览器支持)
unloaded
2. beforeunload 事件(页面卸载前检测)
用于检测「用户关闭标签页、关闭浏览器、跳转到其他页面、刷新页面」等会导致页面卸载的场景。
核心原理
当页面即将被卸载时触发 事件,可在此时执行收尾操作(如保存数据、上报行为),但浏览器对该事件的限制较多(防止恶意弹窗)。
beforeunload
代码实现
// 监听页面卸载前事件
window.addEventListener('beforeunload', function(e) {
// 1. 执行必要的收尾操作(同步操作,异步操作可能被浏览器中断)
console.log('用户即将离开页面(卸载)');
saveUserProgress(); // 同步保存用户进度
// 2. (可选)提示用户是否确认离开(部分浏览器限制弹窗样式)
e.preventDefault(); // 标准做法,部分浏览器需要
e.returnValue = ''; // 必须设置,否则弹窗不生效(Chrome/Firefox)
return ''; // 兼容旧版浏览器
});
// 同步保存用户进度(异步操作可能失效)
function saveUserProgress() {
// 示例:使用 localStorage 同步保存(不要用 fetch/axios 异步请求)
localStorage.setItem('lastProgress', JSON.stringify({ time: Date.now() }));
}
关键说明
触发场景:✅ 关闭标签页 / 浏览器✅ 点击链接跳转到其他页面✅ 刷新页面✅ 在地址栏输入新 URL 并访问❌ 切换标签页 / 最小化浏览器(不会触发,页面未卸载)重要限制:
事件处理函数中,异步操作(如 fetch、setTimeout)可能被浏览器中断,因此只能执行同步操作(如 localStorage、sessionStorage)。浏览器对弹窗提示的样式和文案有严格限制,无法自定义文案(统一显示浏览器默认提示),且部分浏览器(如 Safari)可能完全禁用弹窗。不要滥用该事件,频繁触发会影响用户体验,浏览器可能限制其执行。
3. unload 事件(页面卸载完成检测)
事件在页面完全卸载时触发,比
unload 晚,几乎无法执行有效操作,仅用于兜底检测。
beforeunload
代码实现
window.addEventListener('unload', function() {
console.log('页面已卸载');
// 注意:此处异步操作几乎都会被浏览器取消,同步操作也可能失效
// 仅建议做极简单的同步操作,不依赖执行结果
});
关键说明
触发场景:与 一致,但执行时机更晚。限制:浏览器会优先终止页面资源,因此该事件中的代码执行极不可靠,不建议依赖此事件处理核心业务。
beforeunload
4. focus/blur 事件(窗口焦点检测)
通过监听浏览器窗口的焦点变化,辅助判断用户是否离开页面(兼容性好,但精准度低于 Page Visibility API)。
代码实现
// 窗口失去焦点(用户切换到其他窗口/应用)
window.addEventListener('blur', function() {
console.log('窗口失去焦点(用户可能离开)');
});
// 窗口获得焦点(用户切回当前窗口)
window.addEventListener('focus', function() {
console.log('窗口获得焦点(用户返回)');
});
关键说明
触发场景:✅ 切换到其他应用(如微信、Excel)✅ 切换到浏览器的其他窗口✅ 点击浏览器地址栏 / 书签栏❌ 切换到同一浏览器的其他标签页(部分浏览器不触发,如 Chrome)缺点:精准度低,无法区分 “切换到其他标签页” 和 “切换到其他应用”,仅作为辅助方案。
二、组合方案:覆盖所有场景
为了全面检测用户 “离开页面” 的所有场景,建议结合 Page Visibility API 和 beforeunload 事件:
// 方案1:检测页面可见性变化(切换标签、最小化、锁屏)
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
console.log('用户离开页面(不可见)');
handlePageInactive(); // 处理页面不可见逻辑
} else {
console.log('用户返回页面(可见)');
handlePageActive(); // 处理页面可见逻辑
}
});
// 方案2:检测页面卸载(关闭标签、跳转、刷新)
window.addEventListener('beforeunload', function(e) {
console.log('用户即将卸载页面');
handlePageUnload(); // 处理页面卸载前逻辑
e.preventDefault();
e.returnValue = '';
return '';
});
// 页面不可见时的处理逻辑
function handlePageInactive() {
// 暂停视频、定时器、动画等
pauseMedia();
pauseTimers();
// 上报用户离开行为(异步请求,此处可执行,因为页面未卸载)
fetch('/api/user/inactive', { method: 'POST' });
}
// 页面可见时的处理逻辑
function handlePageActive() {
// 恢复视频、定时器、动画等
resumeMedia();
resumeTimers();
}
// 页面卸载前的处理逻辑
function handlePageUnload() {
// 同步保存用户数据(异步请求可能失效)
saveUserStateSync();
}
// 示例:暂停/恢复视频
function pauseMedia() {
const media = document.querySelectorAll('video, audio');
media.forEach(el => el.pause());
}
function resumeMedia() {
const media = document.querySelectorAll('video, audio');
media.forEach(el => el.play().catch(err => console.log('自动播放被阻止', err)));
}
// 示例:暂停/恢复定时器(需存储定时器ID)
let timerId = setInterval(() => console.log('定时器运行'), 1000);
function pauseTimers() {
clearInterval(timerId);
}
function resumeTimers() {
timerId = setInterval(() => console.log('定时器运行'), 1000);
}
// 示例:同步保存用户状态
function saveUserStateSync() {
const state = {
lastAction: Date.now(),
page: window.location.href
};
localStorage.setItem('userState', JSON.stringify(state));
}
三、常见问题与解决方案
问题 1:beforeunload 中的异步请求失效
原因:浏览器在页面卸载时会中断异步操作,优先释放资源。解决方案:
使用 :这是浏览器提供的专门用于页面卸载时发送异步请求的 API,能保证请求被发送(即使页面已卸载)。
navigator.sendBeacon()
window.addEventListener('beforeunload', function() {
// sendBeacon 发送异步请求,浏览器会保证其完成
const data = new FormData();
data.append('action', 'page_unload');
data.append('time', Date.now().toString());
navigator.sendBeacon('/api/user/unload', data);
});
关键限制: 仅支持 POST 请求,且数据格式为 FormData/Blob/URLSearchParams,无法设置自定义请求头(如 Token),需通过参数传递鉴权信息。
navigator.sendBeacon
问题 2:区分 “关闭标签页” 和 “刷新页面”
难点:浏览器未提供直接区分的 API,需通过间接方式判断。解决方案:
利用 记录页面加载状态:
sessionStorage
// 页面加载时
window.addEventListener('load', function() {
if (sessionStorage.getItem('isRefreshing')) {
console.log('用户刷新了页面');
sessionStorage.removeItem('isRefreshing');
} else {
console.log('用户首次打开页面');
}
});
// beforeunload 时标记为刷新
window.addEventListener('beforeunload', function() {
// 检测是否是刷新操作(通过鼠标事件/键盘事件辅助判断)
let isRefresh = false;
// 监听 F5 刷新键
window.addEventListener('keydown', function(e) {
if (e.key === 'F5' || (e.ctrlKey && e.key === 'r')) {
isRefresh = true;
}
});
// 监听浏览器刷新按钮点击(无法直接监听,需结合其他逻辑)
if (isRefresh) {
sessionStorage.setItem('isRefreshing', 'true');
}
});
说明:该方案仅能部分区分,无法 100% 精准判断(如用户点击浏览器刷新按钮无法直接检测),建议仅作为辅助逻辑。
问题 3:移动端适配
移动端的 “离开页面” 场景:切换到其他 App、锁屏、返回桌面、关闭浏览器标签。适配方案:
Page Visibility API 同样适用于移动端浏览器(如 Chrome 移动端、Safari 移动端)。移动端的 事件可能被浏览器限制(如 Safari 移动端禁用弹窗提示),建议优先使用
beforeunload 上报数据。
navigator.sendBeacon()
问题 4:浏览器兼容性
| API / 事件 | 兼容性 | 备注 |
|---|---|---|
| Page Visibility API | IE10+、Chrome 13+、Firefox 10+、Safari 7+ | 移动端浏览器(Chrome/Safari)均支持 |
| beforeunload | IE4+、所有现代浏览器 | 移动端 Safari 对弹窗提示有限制 |
| navigator.sendBeacon | Chrome 39+、Firefox 31+、Safari 11.1+ | 移动端 Chrome 支持,iOS Safari 11.1+ 支持 |
四、业务场景适配建议
| 业务场景 | 推荐方案 |
|---|---|
| 暂停 / 恢复视频、音频播放 | Page Visibility API |
| 暂停 / 恢复定时器、动画 | Page Visibility API |
| 上报用户 “离开页面” 行为(切换标签 / 最小化) | Page Visibility API + fetch 异步请求 |
| 上报用户 “关闭页面” 行为(关闭标签 / 跳转) | beforeunload + navigator.sendBeacon |
| 保存用户进度 / 数据 | beforeunload + localStorage/sessionStorage 同步保存 |
| 提示用户 “是否确认离开”(如未保存表单) | beforeunload 事件(注意浏览器弹窗限制) |
五、总结
判断用户是否离开当前页面,核心是区分「页面不可见」和「页面卸载」两类场景:
「页面不可见」(切换标签、最小化、锁屏):优先使用 Page Visibility API,精准且无副作用。「页面卸载」(关闭标签、跳转、刷新):使用 beforeunload 事件,结合 保证异步请求生效,同步操作存储关键数据。辅助方案:focus/blur 事件可作为补充,但精准度较低,不建议单独使用。
navigator.sendBeacon
实际开发中,需结合业务需求选择合适的方案,同时注意浏览器的限制(如异步请求、弹窗提示),确保功能稳定且不影响用户体验。



