现代 Web 开发中检测用户离开页面的完整方案(附 Vue 实现)
现代 Web 开发中检测用户离开页面的完整方案(附 Vue 实现)
在现代 Web 应用开发中,准确判断用户是否停留在当前页面是一个重要需求。这不仅关系到用户体验优化,还影响着数据分析准确性和系统资源利用率。本文将详细介绍前端检测用户离开页面的各种场景及解决方案,并提供 Vue 框架下的实现示例。
一、用户"离开页面"的场景细分
首先我们需要明确,"离开页面"并非单一行为,它包含多种场景:
- 切换到其他浏览器标签页或应用(页面不可见但未关闭)
- 最小化浏览器窗口(页面不可见)
- 关闭浏览器标签页或整个浏览器
- 在当前标签页导航到新 URL
- 移动设备上切换到其他 App 或返回主屏幕
- 使用浏览器"前进/后退"按钮导航
不同场景需要不同的检测方案,我们需要根据实际业务需求选择合适的技术组合。
二、核心检测技术与 API
1. Page Visibility API(页面可见性 API)- 现代首选
这是处理"页面是否对用户可见"的标准方案,专门用于检测页面隐藏/显示状态,适合处理切换标签页、最小化窗口等场景。
核心属性与事件:
document.hidden
:只读属性,页面不可见时返回true
visibilitychange
:当页面可见性状态变化时触发
适用场景:
- 暂停/恢复视频、音频播放
- 停止/启动动画或轮播
- 暂停后台数据轮询,恢复可见时继续
- 切换主题色或通知状态
Vue 实现示例:
<template><div><video ref="myVideo" src="example.mp4" autoplay loop></video></div>
</template><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 需要设置 returnValuee.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 项目中的统一封装示例:
<!-- usePageLeave.js 组合式函数 -->
<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 的处理。
通过准确检测用户行为并做出相应处理,我们可以优化资源利用、提升用户体验,并收集更准确的分析数据,为产品迭代提供有力支持。