这个是前端兼容性系列内容
因为前端监控会在页面关闭的时候,发送一下日志,所以会涉及到监听页面关闭,之前我们只监听了一个beforeunload 来发送数据
但是我看了之后发现应该没有这么简单实现,前端总要写一些乱七八糟的兼容代码的啊!
于是就去研究了一下,好家伙,兼容性五花八门
我测试的终端包括
1、Windows PC (10),Chrome
2、iOS(14,13,12,11),Safari
3、Android (10,9),自带浏览器
4、HarmonyOS(1),自带浏览器
是的,我还测试了鸿蒙,华为 yyds!
下面就来详细说说,本文分为
1、页面关闭动作
2、页面关闭事件
3、测试结论
4、兼容做法
5、页面关闭发送请求
亲身多次实验,but 数据仅供参考
页面关闭动作
我仔细想了想所有会导致页面关闭的动作
1、页面刷新
2、跳转页面
3、关闭tab
4、关闭浏览器
所以如果我要监听页面关闭,那么我必须要都兼容这些动作。
我是怎么做这些动作的,关闭tab ,pc 的不用说了吧
移动端就是打开浏览器的窗口界面,然后关闭
关闭浏览器则是在任务管理界面,把 app 划出
页面关闭事件
页面关闭有哪些事件,我直接列出来
1、beforeunload
2、pagehide
3、unload
它们触发的顺序和列出来的一样,beforeunload->pagehide->unload
下面来看针对这些事件的兼容情况
测试结论
PC 端对于上面 四个动作,3个事件 都支持,移动端则表现不一
先综述一下
1、 iOS 压根就不支持 beforeunload,unload 根据 iOS 版本支持程度也较低
2、Android 只有刷新支持 beforeunload,而 unload 的话好一些,支持 刷新和关闭tab
3、HarmonyOs ,刷新和跳转 支持 beforeunload,unload 只有 刷新支持
具体数据如下
所以综上所述,beforeunload 和 unload 在移动端并不是十分可靠
而 iOS 开发文档也说明了,load 这类事件支持不好,最好使用 pagehide 事件
https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html#//apple_ref/doc/uid/TP40006511-SW5
经过测试,结论如下
pagehide 的确支持程度要好很多,不管是PC 还是移动端,但是终究没能全部覆盖,有点遗憾啊,难道要抛弃这部分了吗
转念想了想, visibilitychange会在页面可见或隐藏时触发,或许能解决掉一部分
测试了一下,如下
WC,完全对 iOS 不支持啊,但是可以看见的确解决了一部分问题,把上面 HarmonyOs 、Android 都支持了
所以现在就剩两种情况无法监听到页面关闭了
1、关闭 tab 时,iOS14 以下(iOS13、iOS12、iOS11,其他版本未测)
2、关闭浏览器时,iOS 全不支持
这两种情况也没有什么好的办法,但是考虑到在移动端关闭应用通常是App切到后台然后再上滑关闭
而 iOS 在把浏览器切后台的时候,可以触发 visibilitychange ,所以可以算是解决掉 关闭浏览器的问题
至于关闭 tab,我调查大概四五个人,很少有关闭tab 的习惯,所以也不算是什么大问题
兼容做法
所以现在如果我们要监听页面关闭,那么我们最好监听四种事件,这样可以最大程度兼容
使用一个变量去判定是否已经执行过 页面关闭相关的处理逻辑
只要执行就行,谁执行没有关系,大家排好队
let isEndSendOK = false;
function report() {
if (isEndSendOK) {
return;
}
isEndSendOK = true;
fetch('xxxxxx');
}
// 监听多个事件,做同一个事情,用一个标志位确定是否做过
// 移动端普遍只支持 pagehide
window.addEventListener('beforeunload', report);
window.addEventListener('pagehide', report);
window.addEventListener('unload', report);
// IOS14 之前不会冒泡,只能监听document
document.addEventListener('visibilitychange', () => {
if (document.visibilityState !== 'visible') {
report();
} else {
// 如果界面又显示了,说明没有关闭,重置标志位
isEndSendOK = false;
}
});
页面关闭发送请求
在页面关闭的时候发送请求,因为请求是异步的,所以大多数时候并不一定成功
使用同步的方式发送请求是可以,但是 会迫使用户代理延迟卸载文档,并使得下一个导航出现的更晚。
会导致非常差的下一个页面载入性能,所以如果你的页面在体验发现加载慢,有可能是上一个页面的锅
上一个使用 同步方式发送 XHR 的例子
function xhrSync(type) {
var request = new XMLHttpRequest();
request.open(
'POST',
'https://ke.qq.com/report' ,
false //false表示同步请求
);
request.send('xhr');
}
并且现在有部分浏览器针对这个情况,已经不允许在页面关闭的时候发送同步请求了,不然就会报错
Chrome 在文档中也有相关的说明
https://www.chromestatus.com/feature/4664843055398912
Chrome now disallows synchronous XHR during page dismissal when the page is being navigated away from or closed by the user. This involves the following events (when fired on the path of page dismissal): beforeunload, unload, pagehide, and visibilitychange
反正就是不给你发!不然我报错!
反正异步发送不靠谱,同步又不好
异步到底有多步靠谱,我还做了个测试,看看异步和同步的支持情况,数据如下,仅供参考
表示能成功收到请求,
表示收不到请求,Error 表示报错
你可能会问,你关闭 tab 和 浏览器,你是怎么抓到请求的,因为我的页面用 whistle 代理,请求会经过 whistle,所以可以在界面上看到所有抓到的请求,不会的可以参考 前端调试必备-whistle 入门
你可能会问,你关闭 tab 和 浏览器,你是怎么抓到请求的
因为我的页面用 whistle 代理,请求会经过 whistle,所以可以在界面上看到所有抓到的请求
反正就是不行!
针对这个情况, navigator.sendBeacon() 方法就出现了
会使用户代理在有机会时异步地向服务器发送数据,同时不会延迟页面的卸载或影响下一导航的载入性能。这就解决了提交分析数据时的所有的问题:数据可靠,传输异步并且不会影响下一页面的加载
yyds!
但是好像支持情况也不太好嘛,IE 再见了
经过实测,只有 iOS11 没有sendBeacon 这个方法,其他的 HarmonyOS ,Android,PC、iOS11以上 都能成功发送请求
但是呢,诶,但是,哪里有这么完美的东西呢
sendBeacon 只支持发送少量数据,如果发送太大的数据,是会报错的
具体是返回一个false,表示该请求无法加入传输队列
我查的资料以及自己尝试,最大是 64KB,多1B 都不行
例子我是拷贝的
function testBeaconLimit() {
var url = 'https://ke.qq.com/report';
var n = 1024 * 64;
// this method courtesy of http://stackoverflow.com/questions/14343844/create-a-string-of-variable-length-filled-with-a-repeated-character
var data = new Array(n + 1).join('X'); // generate string of length n
if (!navigator.sendBeacon(url, data)) {
alert('data limit reached');
}
}
并且还有一个要注意的
虽然说是最大 64KB,但是频繁发送64KB 也有可能会错误,所以使用 sendBeacon 一定要做好错误兼容哦
至于这里发送请求的兼容做法的话
如果你不在乎性能,可以先使用 sendBeacon 发送,不支持或者报错,再使用同步的 XMLHttpRequest
但是因为同步的 xhr 可能会报错,也要做好错误处理
我搜到一个开源库的sendBeacon兼容处理
https://github.com/miguelmota/Navigator.sendBeacon/blob/master/sendbeacon.js
可以参考一下