判断用户是否离开了当前页面
2025/10/13大约 10 分钟
在现代 Web 开发中,我们常常需要知道用户是否还停留在当前页面。这对于优化用户体验、管理资源消耗、统计活跃用户等场景非常重要。
离开页面这个行为本身可以被细分为多种场景:
- 切换到其他浏览器标签页或应用(页面变为不可见,但未关闭)
- 最小化浏览器窗口(同上)
- 关闭浏览器标签页或整个浏览器
- 在当前标签页中导航到新的 URL
- 在移动设备上切换到其他 App 或返回主屏幕
Page Visibility API
Page Visibility API 是检测页面可见性变化的最佳方案,它能准确地告诉我们用户是否正在浏览当前页面。
基本概念
document.visibilityState: 返回当前文档的可见性状态,可能的值有:visible: 页面内容至少部分可见hidden: 页面内容对用户不可见prerender: 页面内容正在被预渲染,但对用户不可见unloaded: 页面即将被卸载
document.hidden: 布尔值,表示页面是否对用户隐藏visibilitychange事件:当页面可见性状态发生变化时触发
实现代码
// 获取页面可见性状态
export function getPageVisibilityState() {
// 兼容不同浏览器前缀
return document.visibilityState || document.webkitVisibilityState || document.mozVisibilityState || document.msVisibilityState || 'visible';
}
// 检测页面是否可见
export function isPageVisible() {
// 检查 visibilityState 或 hidden 属性
if (typeof document.visibilityState !== 'undefined') {
return document.visibilityState === 'visible';
}
if (typeof document.hidden !== 'undefined') {
return !document.hidden;
}
// 检查带前缀的 hidden 属性
if (typeof document.webkitHidden !== 'undefined') {
return !document.webkitHidden;
}
if (typeof document.mozHidden !== 'undefined') {
return !document.mozHidden;
}
if (typeof document.msHidden !== 'undefined') {
return !document.msHidden;
}
// 降级方案:返回默认可见
return true;
}
// 监听页面可见性变化
export function onVisibilityChange(callback) {
if (typeof callback !== 'function') return;
// 兼容不同浏览器的事件名
const visibilityChangeEvent = 'visibilitychange' in document
? 'visibilitychange'
: 'webkitvisibilitychange' in document
? 'webkitvisibilitychange'
: 'mozvisibilitychange' in document
? 'mozvisibilitychange'
: 'msvisibilitychange';
const handleVisibilityChange = () => {
callback(getPageVisibilityState(), isPageVisible());
};
document.addEventListener(visibilityChangeEvent, handleVisibilityChange, false);
// 返回取消监听的函数
return () => {
document.removeEventListener(visibilityChangeEvent, handleVisibilityChange, false);
};
}使用示例
// 监听页面可见性变化
const unsubscribe = onVisibilityChange((visibilityState, isVisible) => {
console.log('页面可见性状态:', visibilityState);
console.log('页面是否可见:', isVisible);
if (isVisible) {
// 用户回到了页面,恢复或更新某些操作
console.log('用户回到了页面');
resumePageActivities();
} else {
// 用户离开了页面,暂停或保存某些操作
console.log('用户离开了页面');
pausePageActivities();
}
});
// 需要时取消监听
// unsubscribe();
function resumePageActivities() {
// 恢复动画、继续播放媒体、重新启动轮询等
}
function pausePageActivities() {
// 暂停动画、暂停播放媒体、停止轮询等
}浏览器兼容性
相关信息
- 表格中的数字表示该浏览器开始支持的最低版本
- "No" 表示该浏览器不支持此功能
- 带前缀表示需要使用浏览器前缀(如 webkit、moz、ms)
PC 端
| 浏览器功能 | Chrome | Edge | Firefox | Opera | Safari | IE |
|---|---|---|---|---|---|---|
| document.visibilityState | 33 | 12 | 18 | 20 | 7 | 10 |
| document.hidden | 33 | 12 | 18 | 20 | 7 | 10 |
| visibilitychange 事件 | 33 | 12 | 18 | 20 | 7 | 10 |
| 带前缀的实现 | 13+ (webkit) | 12+ | 10+ (moz) | 15+ (webkit) | 6+ (webkit) | 10 (ms) |
移动端
| 浏览器功能 | Chrome Android | Firefox Android | Opera Android | Safari iOS | WebView Android |
|---|---|---|---|---|---|
| document.visibilityState | 33 | 18 | 20 | 7 | 4.4.3 |
| document.hidden | 33 | 18 | 20 | 7 | 4.4.3 |
| visibilitychange 事件 | 33 | 18 | 20 | 7 | 4.4.3 |
| 带前缀的实现 | 18+ (webkit) | 14+ (moz) | 14+ (webkit) | 6+ (webkit) | 4.4.3+ |
beforeunload & unload
beforeunload 和 unload 事件用于检测用户何时准备离开页面(刷新、关闭标签页或导航到其他URL)。
基本概念
beforeunload事件:在窗口、文档及其资源即将卸载时触发,可以阻止页面卸载unload事件:在页面正在被卸载时触发,此时已无法取消卸载过程
实现代码
// 监听页面关闭或刷新事件
export function onPageUnload(beforeunloadCallback, unloadCallback) {
// beforeunload 事件 - 用户即将离开页面
if (typeof beforeunloadCallback === 'function') {
const handleBeforeUnload = (event) => {
const returnValue = beforeunloadCallback(event);
// 设置 returnValue 可以触发确认对话框
if (returnValue !== undefined) {
event.returnValue = returnValue;
return returnValue;
}
};
window.addEventListener('beforeunload', handleBeforeUnload);
}
// unload 事件 - 用户正在离开页面(此时已无法取消)
if (typeof unloadCallback === 'function') {
window.addEventListener('unload', unloadCallback);
}
// 返回取消监听的函数
return () => {
if (typeof beforeunloadCallback === 'function') {
window.removeEventListener('beforeunload', handleBeforeUnload);
}
if (typeof unloadCallback === 'function') {
window.removeEventListener('unload', unloadCallback);
}
};
}使用场景
- 提示用户保存未提交的表单数据
- 清理临时资源或断开长连接
- 记录用户离开页面的时间点
浏览器兼容性
相关信息
- 表格中的数字表示该浏览器开始支持的最低版本
- "No" 表示该浏览器不支持此功能
- 注意:现代浏览器对这些事件的行为进行了限制以提高性能
PC 端
| 浏览器功能 | Chrome | Edge | Firefox | Opera | Safari | IE |
|---|---|---|---|---|---|---|
| beforeunload 事件 | 1 | 12 | 1 | 4 | 3 | 4 |
| unload 事件 | 1 | 12 | 1 | 4 | 3 | 4 |
| 触发确认对话框 | 所有版本 | 所有版本 | 所有版本 | 所有版本 | 所有版本 | 所有版本 |
| 自定义消息显示 | 不支持(从92开始) | 不支持(从92开始) | 不支持(从60开始) | 不支持 | 不支持 | 支持 |
移动端
| 浏览器功能 | Chrome Android | Firefox Android | Opera Android | Safari iOS | WebView Android |
|---|---|---|---|---|---|
| beforeunload 事件 | 支持 | 支持 | 支持 | 有限支持 | 支持 |
| unload 事件 | 支持 | 支持 | 支持 | 不可靠 | 支持 |
| 触发确认对话框 | 有限支持 | 有限支持 | 有限支持 | 不支持 | 有限支持 |
注意事项
- 过多使用
beforeunload对话框会影响用户体验,应仅在必要时使用 - 现代浏览器对
unload事件中的异步操作支持有限,应尽量使用同步代码 - 在移动设备上,这些事件的触发行为可能不太一致
- 现代浏览器已不再显示自定义的
beforeunload消息,而是显示浏览器默认消息
pagehide & pageshow
pagehide 和 pageshow 事件是 HTML5 引入的,专门用于处理页面的显示和隐藏状态,尤其适用于移动端。
基本概念
pageshow事件:当页面加载或从缓存中恢复时触发pagehide事件:当页面隐藏或开始进入缓存时触发event.persisted属性:表示页面是否从缓存中加载或是否会被缓存
实现代码
// 监听页面显示和隐藏事件
export function onPageShowHide(pageshowCallback, pagehideCallback) {
// pageshow 事件 - 页面显示时触发(包括首次加载和从缓存恢复)
if (typeof pageshowCallback === 'function') {
const handlePageShow = (event) => {
// persisted 属性表示页面是否从缓存恢复
pageshowCallback(event, event.persisted);
};
window.addEventListener('pageshow', handlePageShow);
}
// pagehide 事件 - 页面隐藏时触发
if (typeof pagehideCallback === 'function') {
const handlePageHide = (event) => {
// persisted 属性表示页面是否会被缓存
pagehideCallback(event, event.persisted);
};
window.addEventListener('pagehide', handlePageHide);
}
// 返回取消监听的函数
return () => {
if (typeof pageshowCallback === 'function') {
window.removeEventListener('pageshow', handlePageShow);
}
if (typeof pagehideCallback === 'function') {
window.removeEventListener('pagehide', handlePageHide);
}
};
}使用场景
- 检测页面是否被缓存(通过 persisted 属性)
- 在移动端更可靠地检测页面切换
- 实现更精确的页面访问统计
浏览器兼容性
相关信息
- 表格中的数字表示该浏览器开始支持的最低版本
- "No" 表示该浏览器不支持此功能
- 这些事件在移动端的兼容性比 beforeunload 和 unload 更好
PC 端
| 浏览器功能 | Chrome | Edge | Firefox | Opera | Safari | IE |
|---|---|---|---|---|---|---|
| pageshow 事件 | 3 | 12 | 1.5 | 15 | 4 | 11 |
| pagehide 事件 | 3 | 12 | 1.5 | 15 | 4 | 11 |
| persisted 属性 | 3 | 12 | 1.5 | 15 | 4 | 11 |
移动端
| 浏览器功能 | Chrome Android | Firefox Android | Opera Android | Safari iOS | WebView Android |
|---|---|---|---|---|---|
| pageshow 事件 | 18 | 4 | 14 | 3.2 | 37 |
| pagehide 事件 | 18 | 4 | 14 | 3.2 | 37 |
| persisted 属性 | 18 | 4 | 14 | 3.2 | 37 |
组合使用方案
为了全面覆盖各种页面状态变化场景,我们可以组合使用上述 API:
// 完整的页面状态监听方案
export class PageVisibilityMonitor {
constructor(options = {}) {
this.onVisibilityChange = options.onVisibilityChange;
this.onBeforeUnload = options.onBeforeUnload;
this.onUnload = options.onUnload;
this.onPageShow = options.onPageShow;
this.onPageHide = options.onPageHide;
this.unsubscribers = [];
}
start() {
// 监听页面可见性变化
if (this.onVisibilityChange) {
this.unsubscribers.push(onVisibilityChange(this.onVisibilityChange));
}
// 监听页面卸载事件
if (this.onBeforeUnload || this.onUnload) {
this.unsubscribers.push(onPageUnload(this.onBeforeUnload, this.onUnload));
}
// 监听页面显示/隐藏事件
if (this.onPageShow || this.onPageHide) {
this.unsubscribers.push(onPageShowHide(this.onPageShow, this.onPageHide));
}
}
stop() {
// 取消所有监听
this.unsubscribers.forEach(unsubscribe => unsubscribe());
this.unsubscribers = [];
}
}
// 使用示例
const monitor = new PageVisibilityMonitor({
onVisibilityChange: (visibilityState, isVisible) => {
console.log('页面可见性变化:', { visibilityState, isVisible });
},
onBeforeUnload: (event) => {
// 仅在有未保存的数据时返回消息
if (hasUnsavedChanges()) {
return '您有未保存的更改,确定要离开吗?';
}
},
onPageShow: (event, persisted) => {
console.log('页面显示:', { persisted });
// 从缓存恢复时可能需要刷新数据
if (persisted) {
refreshData();
}
}
});
// 启动监听
monitor.start();
// 页面卸载前停止监听
window.addEventListener('beforeunload', () => {
monitor.stop();
});API 比较和最佳实践
浏览器兼容性比较表
| API 类型 | 支持的浏览器版本 | 最佳使用场景 | 兼容性注意事项 |
|---|---|---|---|
| Page Visibility API | Chrome 13+/33+, Firefox 10+/18+, Safari 6+/7+, Edge 12+, IE 10+ | 检测页面是否可见(切换标签、最小化窗口等) | 需要处理不同浏览器前缀 |
| beforeunload | Chrome 1+, Firefox 1+, Safari 3+, Edge 12+, IE 4+ | 提示用户保存未提交的更改 | 现代浏览器对触发对话框有严格限制,不再显示自定义消息 |
| unload | 几乎所有桌面浏览器都支持 | 清理资源、记录离开时间 | 在移动设备上不可靠,异步操作有限制 |
| pagehide/pageshow | Chrome 3+, Firefox 1.5+, Safari 4+, Edge 12+, IE 11+ | 移动端页面切换检测、页面缓存状态检测 | 在iOS Safari中比unload更可靠 |
实际应用场景
1. 优化媒体播放
当用户离开页面时,自动暂停视频或音频播放,返回时恢复播放:
// 优化媒体播放体验
const videoElement = document.getElementById('myVideo');
let wasPlaying = false;
onVisibilityChange((state, isVisible) => {
if (isVisible && wasPlaying) {
// 用户回到页面,恢复播放
videoElement.play();
wasPlaying = false;
} else if (!isVisible && !videoElement.paused) {
// 用户离开页面,暂停播放
wasPlaying = true;
videoElement.pause();
}
});2. 智能资源管理
根据页面可见性调整资源消耗,例如调整轮询频率或暂停后台任务:
// 智能资源管理
let pollingInterval;
let pollingRate = 5000; // 默认5秒轮询一次
function startPolling(rate = 5000) {
stopPolling();
pollingRate = rate;
pollingInterval = setInterval(fetchData, pollingRate);
}
function stopPolling() {
if (pollingInterval) {
clearInterval(pollingInterval);
pollingInterval = null;
}
}
// 页面可见时使用较高频率,不可见时降低频率
onVisibilityChange((state, isVisible) => {
if (isVisible) {
// 页面可见,使用高频率轮询
startPolling(5000);
} else {
// 页面不可见,降低轮询频率或完全停止
startPolling(60000); // 改为每分钟轮询一次
}
});
// 初始化轮询
startPolling();3. 用户活动追踪
准确统计用户在页面上的实际停留时间和交互行为:
// 用户活动追踪
let startTime;
let totalVisibleTime = 0;
let isTracking = false;
function startTracking() {
if (!isTracking && isPageVisible()) {
startTime = Date.now();
isTracking = true;
}
}
function stopTracking() {
if (isTracking) {
totalVisibleTime += Date.now() - startTime;
isTracking = false;
startTime = null;
}
}
// 监听页面可见性变化
onVisibilityChange((state, isVisible) => {
if (isVisible) {
startTracking();
} else {
stopTracking();
}
});
// 监听页面卸载事件
onPageUnload(() => {
stopTracking();
// 可以在这里发送统计数据
console.log('用户在页面上的实际停留时间(毫秒):', totalVisibleTime);
// reportAnalytics({ actualTimeSpent: totalVisibleTime });
});
// 初始启动
startTracking();总结
判断用户是否离开页面是前端开发中的常见需求,我们可以根据不同的场景选择合适的 API:
- Page Visibility API 是检测页面可见性变化的最佳选择,兼容性好,性能高,特别适合检测用户切换标签页或最小化窗口的情况
- beforeunload & unload 适用于需要提示用户或清理资源的场景,但在现代浏览器中有较多限制,特别是在移动端
- pagehide & pageshow 特别适合移动端,可以更可靠地检测页面状态变化和缓存情况
兼容性最佳实践
- 优先使用 Page Visibility API:对于检测页面可见性变化,这是最现代、最高效的方案
- 移动端优先考虑 pagehide/pageshow:在移动设备上,这些事件比 beforeunload/unload 更可靠
- 提供降级方案:在不支持现代 API 的环境中,提供合适的降级逻辑
- 谨慎使用 beforeunload:仅在用户有未保存的重要更改时使用
- 测试不同设备和浏览器:确保在目标环境中功能正常工作
更新日志
2025/10/13 03:55
查看所有更新日志
7e15d-于
