现代 Web 开发中检测用户离开页面的完整方案(附 Vue 实现)
在现代 Web 应用开发中,准确判断用户是否停留在当前页面是一个重要需求。这不仅关系到用户体验优化,还影响着数据分析准确性和系统资源利用率。本文将详细介绍前端检测用户离开页面的各种场景及解决方案,并提供 Vue 框架下的实现示例。
一、用户"离开页面"的场景细分
首先我们需要明确,"离开页面"并非单一行为,它包含多种场景:
- 切换到其他浏览器标签页或应用(页面不可见但未关闭)
- 最小化浏览器窗口(页面不可见)
- 关闭浏览器标签页或整个浏览器
- 在当前标签页导航到新 URL
- 移动设备上切换到其他 App 或返回主屏幕
- 使用浏览器"前进/后退"按钮导航
不同场景需要不同的检测方案,我们需要根据实际业务需求选择合适的技术组合。
二、核心检测技术与 API
1. Page Visibility API(页面可见性 API)- 现代首选
这是处理"页面是否对用户可见"的标准方案,专门用于检测页面隐藏/显示状态,适合处理切换标签页、最小化窗口等场景。
核心属性与事件:
document.hidden:只读属性,页面不可见时返回truevisibilitychange:当页面可见性状态变化时触发
适用场景:
- 暂停/恢复视频、音频播放
- 停止/启动动画或轮播
- 暂停后台数据轮询,恢复可见时继续
- 切换主题色或通知状态
Vue 实现示例:
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const myVideo = ref(null)
const handleVisibilityChange = () => {
if (document.hidden) {
// 页面不可见时暂停视频
myVideo.value?.pause()
console.log('用户切换到其他标签页或最小化窗口')
} else {
// 页面恢复可见时继续播放
myVideo.value?.play()
console.log('用户回到当前页面')
}
}
onMounted(() => {
document.addEventListener('visibilitychange', handleVisibilityChange)
})
onUnmounted(() => {
document.removeEventListener('visibilitychange', handleVisibilityChange)
})
</script>
优点:
- W3C 标准,所有现代浏览器支持
- 性能友好,专为可见性检测设计
- 直接反映页面"可见"状态
局限性:
- 无法区分"切换标签页"和"关闭标签页"
- 不能检测页面真正卸载的情况
2. beforeunload 事件 - 防止数据丢失
该事件在页面即将卸载时触发,可用于询问用户是否确定离开,主要用于防止用户意外丢失未保存数据。
Vue 实现示例:
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
// 标记是否有未保存的数据
const hasUnsavedChanges = ref(false)
const handleBeforeUnload = (e) => {
if (hasUnsavedChanges.value) {
// 现代浏览器会忽略自定义文本,使用内置提示
e.preventDefault()
// Chrome 需要设置 returnValue
e.returnValue = ''
return ''
}
}
onMounted(() => {
window.addEventListener('beforeunload', handleBeforeUnload)
})
onUnmounted(() => {
window.removeEventListener('beforeunload', handleBeforeUnload)
})
</script>
注意事项:
- 现代浏览器不允许自定义提示文本,只会显示标准化提示
- 过度使用会影响用户体验,仅在有未保存数据时使用
- 不能依赖此事件执行数据保存操作
3. navigator.sendBeacon() - 可靠数据上报
为解决页面卸载时异步请求不可靠的问题,sendBeacon() 允许异步发送少量数据,浏览器会保证在后台完成发送。
适用场景:
- 用户离开时上报访问时长、操作日志等分析数据
- 记录用户最后操作状态
Vue 实现示例:
<script setup>
import { onMounted, onUnmounted } from 'vue'
// 收集需要上报的分析数据
const getAnalyticsData = () => {
return JSON.stringify({
page: window.location.pathname,
stayTime: Date.now() - window.pageLoadTime,
lastAction: window.lastUserAction
})
}
const handlePageHide = () => {
// 发送数据到服务器,确保页面卸载时能成功提交
navigator.sendBeacon('/api/user-leave', getAnalyticsData())
}
onMounted(() => {
// 记录页面加载时间
window.pageLoadTime = Date.now()
window.addEventListener('pagehide', handlePageHide)
})
onUnmounted(() => {
window.removeEventListener('pagehide', handlePageHide)
})
</script>
优点:
- 异步非阻塞,不影响页面卸载
- 浏览器保证数据发送完成
- 不会延迟页面切换或关闭
4. pagehide 和 pageshow 事件 - 处理往返缓存
现代浏览器(尤其是移动端)使用"往返缓存"(bfcache)优化后退/前进导航,此时 unload 事件可能不触发,pagehide 则更可靠。
核心特性:
pagehide:导航离开页面时触发,无论是否存入 bfcacheevent.persisted:判断页面是否被存入 bfcachepageshow:从缓存恢复页面时触发
Vue 实现示例:
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const handlePageHide = (event) => {
if (event.persisted) {
console.log('页面进入 bfcache,可能会被缓存')
// 保存需要恢复的状态
sessionStorage.setItem('pageState', JSON.stringify({
scrollPosition: window.scrollY
}))
} else {
console.log('页面正常卸载')
}
// 无论哪种情况都发送统计数据
navigator.sendBeacon('/api/page-leave', JSON.stringify({
path: window.location.pathname,
isPersisted: event.persisted
}))
}
const handlePageShow = (event) => {
if (event.persisted) {
console.log('从 bfcache 恢复页面')
// 恢复之前保存的状态
const savedState = sessionStorage.getItem('pageState')
if (savedState) {
const { scrollPosition } = JSON.parse(savedState)
window.scrollTo(0, scrollPosition)
}
}
}
onMounted(() => {
window.addEventListener('pagehide', handlePageHide)
window.addEventListener('pageshow', handlePageShow)
})
onUnmounted(() => {
window.removeEventListener('pagehide', handlePageHide)
window.removeEventListener('pageshow', handlePageShow)
})
</script>
三、综合方案与最佳实践
根据不同业务场景,推荐以下组合方案:
页面可见性监测:
- 优先使用 Page Visibility API
- 适用于暂停/恢复操作、资源优化
数据上报:
- 使用
navigator.sendBeacon()+pagehide事件 - 替代不可靠的
unload事件
- 使用
防止数据丢失:
- 必要时使用
beforeunload - 配合本地存储自动保存草稿
- 必要时使用
处理缓存与恢复:
- 用
pagehide和pageshow处理 bfcache 场景 - 保存/恢复页面关键状态
- 用
Vue 项目中的统一封装示例:
<script setup>
import { onMounted, onUnmounted } from 'vue'
export default function usePageLeave(options) {
const {
onVisibilityChange,
onPageHide,
onBeforeUnload
} = options
// 可见性变化处理
const handleVisibilityChange = () => {
onVisibilityChange?.(document.hidden)
}
// 页面隐藏处理
const handlePageHide = (event) => {
onPageHide?.(event)
}
// 页面卸载前处理
const handleBeforeUnload = (event) => {
onBeforeUnload?.(event)
}
onMounted(() => {
document.addEventListener('visibilitychange', handleVisibilityChange)
window.addEventListener('pagehide', handlePageHide)
if (onBeforeUnload) {
window.addEventListener('beforeunload', handleBeforeUnload)
}
})
onUnmounted(() => {
document.removeEventListener('visibilitychange', handleVisibilityChange)
window.removeEventListener('pagehide', handlePageHide)
if (onBeforeUnload) {
window.removeEventListener('beforeunload', handleBeforeUnload)
}
})
}
</script>
使用方式:
<script setup>
import usePageLeave from './usePageLeave'
usePageLeave({
onVisibilityChange: (isHidden) => {
if (isHidden) {
console.log('页面不可见')
// 暂停视频、动画等
} else {
console.log('页面可见')
// 恢复播放
}
},
onPageHide: (event) => {
console.log('页面即将离开', event.persisted)
// 发送统计数据
navigator.sendBeacon('/api/log', JSON.stringify({
action: 'leave',
path: window.location.pathname
}))
},
onBeforeUnload: (event) => {
// 检查是否有未保存数据
if (hasUnsavedChanges.value) {
event.preventDefault()
event.returnValue = ''
return ''
}
}
})
</script>
四、总结
检测用户离开页面是一个涉及多种场景的复杂需求,没有单一解决方案能覆盖所有情况。通过合理组合 Page Visibility API、pagehide 事件和 sendBeacon() 等现代 API,我们可以构建可靠的检测系统。
在 Vue 项目中,建议将这些逻辑封装为组合式函数,以便在多个组件中复用。同时要注意不同浏览器的兼容性差异,特别是移动端对 bfcache 的处理。
通过准确检测用户行为并做出相应处理,我们可以优化资源利用、提升用户体验,并收集更准确的分析数据,为产品迭代提供有力支持。
浙公网安备 33010602011771号