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

丑话说在前

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

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

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

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

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

  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这也就努力挣扎一下,把测试结果发给评审人员评审一下吧~

相关推荐
多多*1 分钟前
OJ在线评测系统 登录页面开发 前端后端联调实现全栈开发
linux·服务器·前端·ubuntu·docker·前端框架
2301_801074151 分钟前
TypeScript异常处理
前端·javascript·typescript
小阿飞_3 分钟前
报错合计-1
前端
caperxi4 分钟前
前端开发中的防抖与节流
前端·javascript·html
霸气小男4 分钟前
react + antDesign封装图片预览组件(支持多张图片)
前端·react.js
susu10830189115 分钟前
前端css样式覆盖
前端·css
学习路上的小刘7 分钟前
vue h5 蓝牙连接 webBluetooth API
前端·javascript·vue.js
&白帝&7 分钟前
vue3常用的组件间通信
前端·javascript·vue.js
小白小白从不日白18 分钟前
react 组件通讯
前端·react.js
Redstone Monstrosity35 分钟前
字节二面
前端·面试