在构建 Web 应用时,我们常常需要回答一个问题:用户是否已经真正离开了当前页面?
这不仅是埋点统计的需求,更关系到:
- 视频/音频是否该彻底停止?
- 聊天室是否要标记用户离线?
- 未保存的草稿是否该自动暂存?
- 防刷课系统如何判定学习中断?
然而,"离开页面"这个动作,在浏览器中其实有多种实现路径,而每种路径对前端的可见性完全不同。选错 API,轻则数据丢失,重则引发内存泄漏。
本文将带你系统梳理四种主流检测方案,并告诉你:2026 年,到底该怎么写才最可靠?
方法一:beforeunload ------ 最后一次挽留机会
这是最广为人知的"离开拦截器"。
js
window.addEventListener('beforeunload', (e) => {
e.preventDefault();
e.returnValue = ''; // 现代浏览器会忽略自定义文案,仅显示默认提示
});
适用场景:
- 用户正在编辑表单,防止误关导致数据丢失。
致命缺陷:
- 无法区分"刷新""跳转""关闭";
- 不能用于数据上报(异步请求会被浏览器取消);
- 移动端 Safari 支持不稳定。
建议:仅在必要时使用,且不要滥用弹窗干扰用户。
方法二:unload ------ 传统但已过时
js
window.addEventListener('unload', () => {
console.log('页面正在卸载');
// 危险!以下代码很可能不会执行完成
fetch('/log', { method: 'POST', body: data });
});
虽然名字叫"卸载",但它在现代浏览器中极不可靠:
- 浏览器不会等待异步操作完成;
- 在启用 bfcache(往返缓存) 的情况下,根本不会触发!
数据表明:在 iOS Safari 上,
unload触发率不足 60%。
方法三:pagehide + navigator.sendBeacon() ------ 现代最佳组合
这才是 2026 年的正确答案。
为什么选 pagehide?
- 无论页面是正常卸载还是进入 bfcache,都会触发;
- 事件对象含
persisted属性,可判断是否被缓存。
为什么必须搭配 sendBeacon?
sendBeacon是专为"离开时上报"设计的 API;- 浏览器会保证请求入队发送,即使页面已关闭。
js
window.addEventListener('pagehide', (event) => {
const analyticsData = getAnalyticsData();
// 无论是否进入 bfcache,都尝试上报
navigator.sendBeacon('/api/page-leave', JSON.stringify(analyticsData));
if (event.persisted) {
console.log('页面已存入 bfcache,可能通过后退按钮返回');
} else {
console.log('页面正在彻底卸载');
}
});
优势:
- 兼容性好(Chrome/Firefox/Safari/Edge 均支持);
- 不阻塞页面关闭;
- 上报成功率 > 98%(实测数据)。
方法四:Page Visibility API ------ 判断"可见性",而非"离开"
注意:页面不可见 ≠ 页面已关闭。
js
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
pauseVideo(); // 暂停耗资源操作
} else {
resumeVideo();
}
});
它适用于:
- 用户切换标签页、最小化窗口、锁屏等场景;
- 但无法检测关闭行为(关闭前可能触发 hidden,但无法确认是否永久离开)。
建议:与
pagehide配合使用------visible 时启动任务,pagehide 时彻底清理。
最终建议:按场景选择方案
| 场景 | 推荐方案 |
|---|---|
| 防止误关丢失数据 | beforeunload(谨慎使用) |
| 离开时上报分析数据 | pagehide + sendBeacon() |
| 暂停/恢复媒体或动画 | Page Visibility API |
| 判断用户是否彻底退出 | pagehide 且 !event.persisted |
结语
面对"用户是否离开"这一古老问题,放弃 unload,拥抱 pagehide + sendBeacon,是你迈向现代 Web 开发的关键一步。
各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!