需求: 统计用户在某个页面的停留时间, (排除用户离开标签页的时间)
浏览器标签页生命周期
对web app会话结束标志的定义
一次应用程序会话是指用户从进入应用程序到离开应用程序的整个交互过程。在这个过程中,用户可以进行各种操作,如浏览内容、提交表单、进行搜索等。通常来说会话开始于用户打开应用程序,并在用户关闭应用程序或长时间不活动时结束。
对于页面停留时间的统计上, 一般在会话开始时开启统计, 会话结束时结束并向服务端上传数据。
就目前的需求:
- 页面加载时会话开始
- 页面卸载/失活 后会话结束
- 页面为hidden(暂时离开当前页面)的时间不计入结果
理想态
我们可以把页面的死亡方式分为三类。
- 浏览器杀活。
当长时间离开一个页面时, 浏览器逐步释放标签页占用的资源, 此过程不触发页面卸载相关事件。
- forzen。 此阶段页面的大部分javascript暂停执行, 动画也会停止。
- discarded。 页面的资源完全释放, 重新访问页面时, 页面会重新加载。
由于forzen状态js停止, 所以进入forzen状态前是最后的可以上报数据的时间点。
- SPA页面跳转
SPA的页面跳转虽然视觉上可能和打开新页面无异, 但实际上仍然是同一个页面, 并不会触发页面的卸载事件。
此种死亡方式使用前端框架的组件卸载钩子监听即可。
- 页面卸载
页面卸载有很多种触发方式, 如:
- 关闭标签页
- 关闭浏览器
- 页内地址栏跳转、路由进退
- 等等
页面卸载一般使用pagehide、beforeunload事件监听。
在理想状态中,
- visible到hidden: 使用Page Visibility Api
- 浏览器杀活导致死亡: 监听freeze事件
- SPA页面跳转: 使用react卸载钩子
- 正常标签页卸载: 监听pagehide事件
我们只需要记录页面在visible状态停留的时间, 并在页面死亡时将时间总和上报。
问题一: 页面生命周期API(freeze、resume)兼容性差
其中较为关键的是freeze event, 我们需要用它监听浏览器冻结标签页的行为。
否则当标签页生命结束于冻结时(用户长时间离开标签页), 此前的数据无法被上报。
问题二: 标签页卸载api(pagehide, beforeunload)的触发不可靠(尤其是移动端)
比如以下操作流程并不会触发pagehide、beforeunload事件:
- 用户从浏览器切换到其他app
- 用户在任务管理器后台中杀死浏览器进程
方案一: 忽略杀活和特殊情况(目前选择)
优点: 以页面卸载作为会话结束标志, 符合原需求
缺点: 存在特殊的场景case。
方案二: 使用hidden作为会话结束状态
优点:
- 无论是terminated还是discarded都会经过hidden状态, 覆盖场景全面
- Page Visibility Api 兼容性好
- 逻辑简单
缺点:用户切出a标签页->用户回到a标签页, 会算作两次会话, 不符合目前的产品需求。
Beacon API
浏览器页面的卸载是一个快速的过程。 对于浏览器卸载过程中的XMLHTTPRequest或者fetch请求, 浏览器不保证执行。
Beacon API
是一种用于在页面卸载或隐藏时发送少量数据到服务器的 Web API。它的设计目的是提供一种可靠且高效的方法来发送数据,而不会阻塞页面的卸载过程。Beacon API
适用于发送分析数据、日志信息或其他需要在页面关闭时发送的数据。
主要特点
- 异步发送:
Beacon API
发送数据是异步的,不会阻塞页面的卸载过程。 - 可靠性:即使在页面卸载或隐藏时,
Beacon API
也能确保数据被成功发送。
使用方法
navigator.sendBeacon()
方法是 Beacon API
的核心,用于发送数据。它接受两个参数:目标 URL 和要发送的数据。
语法
navigator.sendBeacon(url, data);
url
:字符串,表示数据发送的目标 URL。data
:可选,表示要发送的数据,可以是ArrayBufferView
、Blob
、DOMString
或FormData
对象等等。