web前端之关闭浏览器标签页后自动退出登录、踩坑之浏览器关闭或刷新前发送可靠的请求、页面卸载的生命周期、监听的不同写法、页面可见性、可视区域、事件、beforeunload、unmounted

智码帮MJ682517 2024-07-11 15:33:03 阅读 52

MENU

初版前言(重要)代码解析功能概括数据属性mounted生命周期钩子unmounted生命周期钩子方法

总结

完整版前言浏览器刷新或关闭时的顺序简易判断Chrome(谷歌浏览器)关闭或刷新行为的方法发送请求总结

结束语


初版

前言(重要)

<code>beforeunload和unload之间的时间关系,也是实现关闭标签自动退出登录的关键。

1、当页面刷新的时候beforeunloadunload之间的时间间隔比较长,亲测时是10毫秒以上。这不是绝对值,要看电脑配置和网络状态是否良好,网上建议5毫秒,但是亲测不行;

2、当页面关闭时,beforeunloadunload之间的时间间隔非常短,具体是多少没有做过测试,反正远远小于10毫秒;

3、所以本案例并不完美,慎用,希望有完美案例的博主可以分享给大家使用。


代码

data() {

return {

beforeUnload_time: 0,

};

},

mounted() {

// 页面(浏览器标签页)关闭前

window.addEventListener("beforeunload", (e) => this.runBefor(e));

// 页面(浏览器标签页)关闭后

window.addEventListener("unload", (e) => this.runUnload(e));

// 页面可视时刷新(会加载整个页面,用户体验不是很好,视情况使用)

// document.addEventListener("visibilitychange", this.runVisibilitychange);

},

unmounted() {

window.removeEventListener("beforeunload", (e) => this.runBefor(e));

window.removeEventListener("unload", (e) => this.runUnload(e));

// document.removeEventListener("visibilitychange", this.runVisibilitychange);

},

methods: {

logout() {

// 退出登录

// 跳转到登录页面

},

runVisibilitychange() {

if (!document.hidden) location.reload();

},

runBefor() {

this.beforeUnload_time = new Date().getTime();

},

runUnload() {

let time = new Date().getTime() - this.beforeUnload_time;

if (time <= 10) this.logout();

}

}


解析

功能概括

这是一段Vue.js组件的代码,管理与网页生命周期相关的事件,特别是用户与浏览器标签页窗口的交互。


数据属性

data() {

return {

beforeUnload_time: 0,

};

}

beforeUnload_time用于存储触发beforeunload事件时的时间戳。


mounted生命周期钩子

mounted() {

// 页面(浏览器标签页)关闭前

window.addEventListener("beforeunload", (e) => this.runBefor(e));

// 页面(浏览器标签页)关闭后

window.addEventListener("unload", (e) => this.runUnload(e));

// 页面可视时刷新(会加载整个页面,用户体验不是很好,视情况使用)

// document.addEventListener("visibilitychange", this.runVisibilitychange);

},

mounted生命周期钩子在组件插入DOM时调用。

beforeunload添加事件监听器,捕获用户尝试关闭或刷新标签页窗口时的事件,触发runBefor方法。

unload添加事件监听器,捕获标签页窗口实际关闭时的事件,触发runUnload方法。

visibilitychange(已注释掉)用于检测页面可见性变化的事件监听器。如果取消注释,会触发runVisibilitychange方法。


unmounted生命周期钩子

unmounted() {

window.removeEventListener("beforeunload", (e) => this.runBefor(e));

window.removeEventListener("unload", (e) => this.runUnload(e));

// document.removeEventListener("visibilitychange", this.runVisibilitychange);

}

unmounted生命周期钩子在组件从DOM中移除时调用。

移除在mounted中添加的beforeunload和unload事件监听器。

如果visibilitychange事件监听器已添加,也会在这里移除。


方法

methods: {

logout() {

// 退出登录

// 跳转到登录页面

},

runVisibilitychange() {

if (!document.hidden) location.reload();

},

runBefor() {

this.beforeUnload_time = new Date().getTime();

},

runUnload() {

let time = new Date().getTime() - this.beforeUnload_time;

if (time <= 10) this.logout();

}

}

logout方法用于处理用户退出登录的逻辑,可能包括清除会话数据和重定向用户到登录页面。

runVisibilitychange方法在文档可见时重新加载页面。这个方法被注释掉,可能是因为重新加载整个页面会影响用户体验。

runBefor在触发beforeunload事件时,将当前时间戳设置为beforeUnload_time。

runUnload计算unload和beforeunload事件之间的时间差。如果这个差值小于或等于10毫秒,则触发logout方法。这表明快速从beforeunload到unload的过渡通常发生在用户快速关闭标签页的情况下。


总结

事件监听器中的beforeunload和unload用于捕获用户尝试离开页面和页面实际卸载时的事件。

退出登录逻辑,如果beforeunload和unload之间的时间非常短(<= 10毫秒),则触发退出登录流程。

页面可见性变化时重新加载一个被注释掉的方法,用于在页面变得可见时重新加载整个页面,当前未使用,可能是因为用户体验问题。

清理,在unmounted生命周期钩子中正确清理事件监听器,以防止内存泄漏。


完整版

前言

目前市面上没有任何浏览器可以准确的、通用的和可靠的区分用户关闭或刷新操作。

如果只是单纯想在用户关闭(浏览器或标签页)时发送请求,那么没有任何完美的解决方案;

如果只是想在Chrome(谷歌浏览器)或360浏览器的急速模式和其他基于Chrome(谷歌浏览器)套皮浏览器上实现,那么下面提供一个简单的方法,但是Edge浏览器不支持该方法。Edge是真牛啊,青出于蓝胜于蓝?


浏览器刷新或关闭时的顺序

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


1、当用户关闭浏览器标签、窗口或输入新的URL地址时,首先会触发onbeforeunload事件;

2、在onbeforeunload事件处理完成后,如果用户选择离开页面(关闭或刷新),则会触发onunload事件。


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


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

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内。环境不同亦有可能导致结果不同。


不同浏览器测试(别人的测试结果)

浏览器 关闭/刷新 onbeforeunload onunload 时间间隔
Chrome52 关闭 [x] [x] < 5ms
Chrome52 刷新 [x] [x] > 20ms
Firefox46 关闭 [x] [x] > 200ms
Firefox46 刷新 [x] [x] 10ms~100ms
Edge13 关闭 [x] [] N/A
Edge13 刷新 [x] [x] < 5ms
IE11 关闭 [x] [x] > 10ms
IE11 刷新 [x] [x] < 5ms


同浏览器不同项目测试(本人的测试结果)

类型 项目一 项目二
关闭 [10 6 8 8 10 9 7 8 7 7 10 4 8 7 5 11 5 6 6 15 6 8 5 9 4 7 6 8 9] (最小时间是4毫秒,最大时间是15毫秒) [4 5 7 4 8 8 4 9 5 7 9 8 7 4 5 7 6 6 5] (最小时间是4毫秒,最大时间是9毫秒)
刷新 [20 20 333 22 21 22 25 20 343 19 20 20 23 19 21 331 25 20 20 20 336 19 21 330 391 22 22 19 21 26 31 25 19 24 19] (最小时间是19毫秒最大时间是391毫秒) [22 323 334 337 20 335 339 333 336 323 21 18 19 19 17 20 20 25 22 20 323 339 335 333 329 321 19] (最小时间是17毫秒最大时间是339毫秒)

根据以上表格,按照需求选择一个合适的分割点来判断浏览器的关闭或刷新操作。

不同网络或不同电脑也会有不同的时间差。

<code>经测试未发现能100%判断清楚关闭或刷新操作,会有误操作,请酌情使用。


发送请求

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


1、使用Navigator.sendBeacon()发送请求

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

// url参数表明data将要被发送到的网络地址

// data(可选)参数是将要发送的ArrayBuffer、ArrayBufferView、Blob、DOMString、FormData或URLSearchParams类型的数据。

// 当用户代理成功把数据加入传输队列时,sendBeacon()方法将会返回true,否则返回false。

navigator.sendBeacon(url, data);

简简单单一行代码即可实现发送可靠的异步请求,同时不会延迟页面的卸载或影响下一导航的载入性能,仅支持POST请求发送数据


2、使用fetch+keepalive发送请求

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

fetch("url", {

method: "GET",

keepalive: true,

});


3、直接发送异步请求

由于从Chrome83开始,onunload里面不允许执行同步的XHR,所以同步请求无法实现,但是异步请求可以实现。异步请求能发送到服务器的成功率并非百分之百,因此不推荐使用。


总结

JavaScript代码

以上便是浏览器关闭或刷新前发送请求的几种方法,本文采用fetch+keepalive尝试简单实现浏览器仅关闭时发送请求,以便让后端知道用户关闭了浏览器,从而转为登出状态。

let beforeTime = 0,

leaveTime = 0;

window.onbeforeunload = () => {

beforeTime = new Date().getTime();

};

window.onunload = () => {

leaveTime = new Date().getTime() - beforeTime;

if (leaveTime <= 5) {

fetch("/api/user/logout", {

method: "GET",

keepalive: true,

});

}

};

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

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


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

vue3代码

<code>import { ref, Ref, onMounted } from "vue";

let beforeUnload_time: Ref<number> = ref(0);

onMounted(() => {

window.onbeforeunload = function (event) {

beforeUnload_time.value = new Date().getTime();

if (event) {

event.returnValue = "此操作将自动退出登录,确定要离开吗?";

return "此操作将自动退出登录,确定要离开吗?";

} else {

return "请使用现代浏览器";

}

};

window.onunload = () => {

let time = new Date().getTime();

time = time - beforeUnload_time.value;

if (time <= 10) {

// 在vue中不需要写完整路径

// 把/api及后面的路径写上即可

fetch("you_url", {

method: "POST",

keepalive: true,

});

}

};

});


结束语

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

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



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。