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

丑话说在前

丑话说在前:当前的浏览器完全没有任何一个可靠、通用、准确区分用户关闭和刷新操作的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这也就努力挣扎一下,把测试结果发给评审人员评审一下吧~

相关推荐
古木20193 分钟前
前端面试宝典
前端·面试·职场和发展
轻口味2 小时前
命名空间与模块化概述
开发语言·前端·javascript
前端小小王2 小时前
React Hooks
前端·javascript·react.js
迷途小码农零零发2 小时前
react中使用ResizeObserver来观察元素的size变化
前端·javascript·react.js
娃哈哈哈哈呀3 小时前
vue中的css深度选择器v-deep 配合!important
前端·css·vue.js
旭东怪3 小时前
EasyPoi 使用$fe:模板语法生成Word动态行
java·前端·word
ekskef_sef5 小时前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
sunshine6415 小时前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻5 小时前
Vue(四)
前端·javascript·vue.js