提问人:zozo 提问时间:5/28/2011 最后编辑:Brian Tompsett - 汤莱恩zozo 更新时间:2/24/2022 访问量:76657
JavaScript、浏览器、窗口关闭 - 发送 AJAX 请求或在窗口关闭时运行脚本
JavaScript, browsers, window close - send an AJAX request or run a script on window closing
问:
我正在尝试找出用户何时离开指定页面。当他使用页面内的链接导航离开时,没有问题,但我有点需要标记一些东西,比如当他关闭窗口或键入另一个 URL 并按回车键时。第二个不是那么重要,但第一个是。所以问题来了:
我如何查看用户何时关闭了我的页面(捕获 window.close 事件),然后...并不重要(我需要发送 AJAX 请求,但如果我能让它运行警报,我可以完成剩下的工作)。
答:
用:
<body onUnload="javascript:">
它应该捕获除关闭浏览器程序之外的所有内容。
评论
有 和 javascript 事件,但这些事件对于 Ajax 请求不可靠(不能保证在其中一个事件中启动的请求会到达服务器)。unload
beforeunload
因此,强烈建议不要这样做,您应该寻找替代方案。
如果您确实需要这个,请考虑使用“ping”风格的解决方案。每分钟发送一次请求,基本上告诉服务器“我还在这里”。然后,如果服务器超过两分钟没有收到此类请求(您必须考虑延迟等),则认为客户端处于脱机状态。
另一种解决方案是使用或执行 Sjax 请求(同步 JavaScript 和 XML),但完全不建议这样做。这样做基本上会冻结用户的浏览器,直到请求完成,他们不会喜欢(即使请求花费的时间很少)。unload
beforeunload
评论
return "a string";
beforeunload
confirm()
我同意 Felix 的想法,我已经用该解决方案解决了我的问题,现在我想清除服务器端解决方案:
1.从客户端向服务器发送请求
2.保存变量中最后一次请求的时间
3.检查服务器时间,并与上次接收的变量进行比较 请求
4.如果结果超过您预期的时间,请开始运行 要在 Windows 关闭时运行的代码...
我也想实现相同的功能,并从 Felix 那里得到了这个答案(不能保证在这些事件之一中发起的请求会到达服务器)。
为了使请求到达服务器,我们尝试了以下代码:-
onbeforeunload = function() {
//Your code goes here.
return "";
}
我们正在使用IE浏览器,现在当用户关闭浏览器时,他会收到确认对话框,因为&等待用户的确认,并且此等待时间使请求到达服务器。return "";
评论
所选答案是正确的,您不能保证浏览器发送 xhr 请求,但根据浏览器的不同,您可以在选项卡或窗口关闭时可靠地发送请求。
通常,浏览器会在 xhr.send() 实际执行之前关闭。Chrome 和 edge 看起来像是在关闭窗口之前等待 javascript 事件循环清空。它们还会在与 javascript 事件循环不同的线程中触发 xhr 请求。这意味着,如果可以使事件循环保持足够长的时间,则 xhr 将成功触发。例如,我测试了发送 xhr 请求,然后数到 100,000,000。对我来说,这在 chrome 和 edge 中都非常一致。如果您使用的是 angularjs,则将对 $http 的调用包装在 $apply 中可以完成相同的操作。
IE似乎有点不同。我不认为IE会等待事件循环清空,甚至不会等待当前堆栈帧清空。虽然它偶尔会正确发送请求,但似乎更频繁地(80%-90% 的时间)是 IE 将在 xhr 请求完全执行之前关闭窗口或选项卡,这导致仅发送部分消息。基本上,服务器收到一个 post 请求,但没有正文。
为了后人,这是我使用附加的代码作为 window.onbeforeunload 侦听器函数:
var xhr = new XMLHttpRequest();
xhr.open("POST", <your url here>);
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
var payload = {id: "123456789"};
xhr.send(JSON.stringify(payload));
for(var i = 0; i < 100000000; i++) {}
我测试了:
Chrome浏览器61.0.3163.100
IE浏览器 11.608.15063.0CO
边缘 40.15063.0.0
评论
1)如果你正在寻找一种在所有浏览器中工作的方法,那么最安全的方法是向服务器发送同步AJAX。这不是一个好方法,但至少要确保你没有向服务器发送太多数据,并且服务器速度很快。
2)您还可以使用异步AJAX请求,并在服务器上使用ignore_user_abort函数(如果您使用的是PHP)。但是,ignore_user_abort很大程度上取决于服务器配置。确保你测试得很好。
3)对于现代浏览器,您不应该发送AJAX请求。您应该使用新的 navigator.sendBeacon 方法异步向服务器发送数据,而不会阻止下一页的加载。由于您希望在用户移出页面之前将数据发送到服务器,因此可以在卸载事件处理程序中使用此方法。
$(window).on('unload', function() {
var fd = new FormData();
fd.append('ajax_data', 22);
navigator.sendBeacon('ajax.php', fd);
});
似乎还有一个用于 sendBeacon 的 polyfill。如果方法本身不可用,则求助于发送同步 AJAX。
对于移动设备很重要:请注意,不保证为移动设备触发 unload 事件处理程序。但是 visibilitychange 事件是肯定被触发的。因此,对于移动设备,您的数据收集代码可能需要进行一些调整。
你可以参考我的博客文章,了解所有 3 种方式的代码实现。
评论
2021 年更新
TL;博士
Beacon API 是此问题的解决方案(几乎在每个浏览器上).
即使用户退出页面,信标请求也应该完成.
您应该何时触发 Beacon 请求?
这将取决于您的用例。如果您希望捕获任何用户出口,(not ) 是开发人员在现代浏览器中可以可靠地观察到的最后一个事件。visibilitychange
unload
注意:只要 的实现在浏览器之间不一致,就可以通过 lifecycle.js 库进行检测。visibilitychange
# lifecycle.js (1K) for cross-browser compatibility
# https://github.com/GoogleChromeLabs/page-lifecycle
<script defer src="/path/to/lifecycle.js"></script>
<script defer>
lifecycle.addEventListener('statechange', function(event) {
if (event.originalEvent == 'visibilitychange' && event.newState == 'hidden') {
var url = "https://example.com/foo";
var data = "bar";
navigator.sendBeacon(url, data);
}
});
</script>
详
即使用户离开页面,信标请求也应该运行完成 - 切换到另一个应用程序等 - 而不会阻止用户工作流程。
在后台,它会发送 POST 请求以及用户凭据 (cookie),但须遵守 CORS 限制。
var url = "https://example.com/foo";
var data = "bar";
navigator.sendBeacon(url, data);
问题是何时发送您的 Beacon 请求.特别是如果您想等到最后一刻才发送会话信息、应用程序状态、分析等。
过去,在活动期间发送它是常见的做法,但由移动用户体验驱动的页面生命周期管理的变化扼杀了这种方法。如今,大多数移动工作流程(切换到新选项卡、切换到主屏幕、切换到另一个应用程序......不要触发 UNLOAD
事件。unload
如果要在用户退出应用/页面时执行操作,现在建议使用 visibilitychange
事件并检查从被动
状态转换为隐藏
状态。
document.addEventListener('visibilitychange', function() {
if (document.visibilityState == 'hidden') {
// send beacon request
}
});
向隐藏状态的转换通常是开发人员可以可靠地观察到的最后一个状态更改(在移动设备上尤其如此,因为用户可以关闭选项卡或浏览器应用本身,并且在这些情况下不会触发 beforeunload、pagehide 和 unload 事件)。
这意味着应将隐藏状态视为用户会话的可能结束。换句话说,保留任何未保存的应用程序状态,并发送任何未发送的分析数据。
本文将详细介绍其详细信息。Page lifecyle API
但是,事件的实现以及跨浏览器的实现并不一致。visibilitychange
Page lifecycle API
在浏览器实现赶上之前,使用生命周期.js库和页面生命周期最佳实践似乎是一个很好的解决方案。
# lifecycle.js (1K) for cross-browser compatibility
# https://github.com/GoogleChromeLabs/page-lifecycle
<script defer src="/path/to/lifecycle.js"></script>
<script defer>
lifecycle.addEventListener('statechange', function(event) {
if (event.originalEvent == 'visibilitychange' && event.newState == 'hidden') {
var url = "https://example.com/foo";
var data = "bar";
navigator.sendBeacon(url, data);
}
});
</script>
有关香草页面生命周期事件(没有生命周期.js)可靠性的更多数字,还有这项研究。
广告拦截器
广告拦截器似乎有阻止发送信标请求的选项。
跨站点请求
Beacon 请求是包含 cookie 的 POST 请求,并受 CORS 规范的约束。
评论
unload and beforeunload aren’t the right events to use with sendBeacon. Instead, use visibilitychange
unload
unload
sendBeacon
Content-Type
试试这个。我在javascript中解决了这个问题,在浏览或关闭选项卡时向服务器发送ajax调用。我在刷新页面时遇到了问题,因为在onbeforeunload功能上,包括刷新页面。performance.navigation.type == 1 应该将刷新与关闭隔离开来(在 Mozzila 浏览器上)。
$(window).bind('mouseover', (function () { // detecting DOM elements
window.onbeforeunload = null;
}));
$(window).bind('mouseout', (function () { //Detecting event out of DOM
window.onbeforeunload = ConfirmLeave;
}));
function ConfirmLeave() {
if (performance.navigation.type == 1) { //detecting refresh page(doesnt work on every browser)
}
else {
logOutUser();
}
}
$(document).bind('keydown', function (e) { //detecting alt+F4 closing
if (e.altKey && e.keyCode == 115) {
logOutUser();
}
});
function logOutUser() {
$.ajax({
type: "POST",
url: GWA("LogIn/ForcedClosing"), //example controller/method
async: false
});
}
评论
在发布问题多年后,我制定了一种更好的实现方法,包括 nodejs 和 socket.io (https://socket.io)(您可以使用任何类型的套接字,但这是我个人的选择)。
基本上,我打开了与客户的连接,当它挂断时,我只是保存数据/做我需要的任何事情。显然,这不能用于显示任何内容/重定向客户端(因为您是在服务器端这样做),但这是我当时实际需要的。
io.on('connection', function(socket){
socket.on('disconnect', function(){
// Do stuff here
});
});
所以。。。现在我认为这将是一种比卸载版本更好的方法(虽然更难实现,因为您需要节点、套接字等,但并不难;如果您第一次这样做,应该需要大约 30 分钟左右)。
评论