工作踩坑之在浏览器关闭/刷新前发送请求

丑话说在前

丑话说在前:当前的浏览器完全没有任何一个可靠、通用、准确区分用户关闭和刷新操作的API。

因此,如果你是单纯想在用户关闭时发送请求,那么没有任何完美答案。如果你只是想在谷歌Chrome 、360浏览器的急速模式 等一众基于谷歌Chrome浏览器套皮浏览器上实现,那么在下面我会提供一个简单的方法,但是Edge并不支持该方法。Edge是真牛啊,青出于蓝胜于蓝?

先来看看浏览器在刷新/关闭时的顺序

为了帮助理解我区分浏览器关闭和刷新操作的方法,先来看看浏览器在关闭/刷新时的执行顺序吧~

在浏览器关闭或刷新页面时,onbeforeunload 和 onunload 事件的执行顺序是固定的。

  1. 当用户关闭浏览器标签、窗口或者输入新的 URL 地址时,首先会触发 onbeforeunload 事件。
  2. 在 onbeforeunload 事件处理完成后,如果用户选择离开页面(关闭或刷新),则会触发 onunload 事件。

因此,onbeforeunload 事件在用户决定离开页面之前执行,而 onunload 事件在用户离开页面之后执行。这两个事件提供了在用户离开页面前后执行代码的机会,可以用于执行清理操作或者提示用户确认离开等操作。通过对比两个事件的执行时间差,我们就可以简单判断浏览器的关闭或刷新行为啦。

简易判断Chrome浏览器关闭或刷新行为的方法

ini 复制代码
let beforeTime = 0,
leaveTime = 0;
// 获取浏览器onbeforeunload时期的时间戳
window.onbeforeunload = () => {
    beforeTime = new Date().getTime();
};
window.onunload = () => {
    // 对比onunload时期和onbeforeunload时期的时间差值
    leaveTime = new Date().getTime() - beforeTime;
    if (leaveTime < 5) {
        // 如果小于5就是关闭
        // 你可以在这发送请求
    } else {
        // 如果大于5就是刷新
        // 你可以在这发送请求
    }
};

注意:经过本人的测试,该方法仅支持Chrome浏览器等,Edge浏览器无论是关闭还是刷新,时间戳差均小于5ms,而谷歌Chrome浏览器的时间戳差均大于5ms,为7ms-8ms左右。环境不同亦有可能导致结果不同。

详见他人的测试结果图:

如何发送请求

既然已经区分了Chrome浏览器的关闭和刷新行为,那么该如果发送请求呢?

发送请求的方式主要有以下几种:

该方法主要用于将统计数据发送到 Web 服务器,同时避免了用传统技术,如XMLHttpRequest所导致的各种问题。

他的使用方法也很简单:

kotlin 复制代码
navigator.sendBeacon(url, data);

// url参数表明 data 将要被发送到的网络地址
// data (可选) 参数是将要发送的 ArrayBuffer、ArrayBufferView、Blob、DOMString、FormData 或 URLSearchParams 类型的数据。
// 当用户代理成功把数据加入传输队列时,sendBeacon() 方法将会返回 true,否则返回 false。

怎么样?简简单单一行代码即可实现发送可靠的异步请求,同时不会延迟页面的卸载或影响下一导航的载入性能。但是别忽略的他很重要的一个特点 :数据是通过 POST 请求发送的。

2. 使用 fetch + keepalive

该方法用于发起获取资源的请求。它返回的是一个 promise。他支持 POST 和 GET 方法,配合 keepalive 参数,可以实现浏览器关闭/刷新行为前发送请求。keepalive可用于超过页面的请求。可以说keepalive就是 Navigator.sendBeacon() 的替代品。

php 复制代码
fetch('url',{
    method:'GET',
    keepalive:true
})

3. 直接发送异步请求

由于从Chrome83开始,onunload里面不允许执行同步的XHR,所以同步请求自然是无法实现的,但是一部请求是可以实现的。但是异步请求发送到设备的成功率并非百分之百,因此并不推荐,也不在此赘述。

总结

以上便是浏览器关闭/刷新前发送请求的几种方法,而我是采用了 fetch + alive 尝试简单实现浏览器仅关闭时发送请求,具体实现代码如下:

ini 复制代码
let beforeTime = 0,
leaveTime = 0;
window.onbeforeunload = () => {
    beforeTime = new Date().getTime();
};
window.onunload = () => {
    leaveTime = new Date().getTime() - beforeTime;
    if (leaveTime <= 5) {
        fetch('/logout.do',{
            method:'GET',
            keepalive:true
        })
    }
};

经测试,使用效果如下

使用该方法对于各浏览器的测试结果

浏览器/测试方法 关闭tab页 关闭浏览器 任务管理器关闭 刷新
Chrome 登出 登出 登出 未登出
Edge 登出 未登出 登出 登出
360急速模式 登出 登出 登出 未登出
360兼容模式 白屏 白屏 白屏 白屏
IE 白屏 白屏 白屏 白屏
浏览器/测试方法 关闭tab页 关闭浏览器 任务管理器关闭 刷新
Chrome √ √ √ √
Edge √ × √ ×
360急速模式 √ √ √ √
360兼容模式 × × × ×
IE × × × ×

小小的吐槽:

后端感知web退出本就不推荐由前端来处理,更优解为 持续ping 或者后端 心跳机制发包 来检测。

既然设备那边提出了这个请求,我们web这也就努力挣扎一下,把测试结果发给评审人员评审一下吧~

相关推荐
传人once1 小时前
多图片上传预览功能如何写
前端·css·html·jquery·html5
Eric.462 小时前
Wan‑Animate+MiniMax-H3 本地部署 AI 漫剧流水线:8G 显存动作迁移与动态分镜工程实战
前端·人工智能·comfyui·ai漫剧
库拉镜像AI牛牛3 小时前
短剧内容自动化生产:知漫剧工作室落地教程
大数据·服务器·前端·人工智能·语音识别
律宏阔3 小时前
Flutter 列表图片内存优化:CachedNetworkImage 与 ClipRRect 的取舍
前端·flutter
行者全栈架构师3 小时前
【鸿蒙心迹】相册文搜图工程化落地:HarmonyOS 7.0 端侧 AI 检索全链路实战与 5 个踩坑解法
前端
律宏阔3 小时前
Dart 3 Record:解决 Future.wait 异构返回值的类型问题
前端·flutter
Seraphina364 小时前
DVWA(XSS Reflected/Stored High,CSRF Low)
前端·xss·csrf
默_笙5 小时前
🍔 中间件不只是打日志:四个钩子、一次短路,和自带的工具
前端·javascript
程序员老赵5 小时前
Docker 部署 Dolibarr:轻松搭建开源 ERP/CRM 平台
运维·前端·后端
星禾元亨5 小时前
同一件事有三份资料,AI 该信哪一份?冲突判定、优先级链与处置流程
前端·人工智能