JavaScript、浏览器、窗口关闭 - 在窗口关闭时发送 AJAX 请求或运行脚本

IT技术 javascript browser window dom-events
2021-02-05 21:25:58

我试图找出用户何时离开指定页面。找出他何时使用页面内的链接导航离开没有问题,但我需要标记一些内容,例如他关闭窗口或键入另一个 URL 并按下 Enter 键。第二个不是那么重要,但第一个很重要。那么问题来了:

我如何才能看到用户何时关闭了我的页面(捕获 window.close 事件),然后...并不重要(我需要发送 AJAX 请求,但如果我可以让它运行警报,我可以做剩下的)。

6个回答

2021 年更新

TL; 博士

Beacon API是这个问题的解决方案(几乎在每个浏览器上)。

即使用户退出页面,信标请求也应该完成。

你应该什么时候触发你的 Beacon 请求?

这将取决于您的用例。如果您想捕获任何用户退出,visibilitychange(not unload) 是开发人员在现代浏览器中可以可靠观察到最后一个事件

注意:只要实施的visibilitychange不同的浏览器并不一致,很容易通过检测到它lifecycle.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>

细节

即使用户离开页面 - 切换到另一个应用程序等 -信标请求也应该运行完成,而不会阻止用户工作流程。

    var URL = "https://example.com/foo";
    var data = "bar";

    navigator.sendBeacon(URL, data);

问题是何时发送您的 Beacon 请求。特别是如果您想等到最后一刻发送会话信息、分析等。

unload活动期间发送它曾经是一种常见的做法,但是页面生命周期管理的变化 - 由移动用户体验驱动 - 扼杀了这种方法。今天,大多数移动工作流程(切换到新选项卡、切换到主屏幕、切换到另一个应用程序......)在任何时候都不会触发该unload事件

如果您想在用户退出您的应用程序/页面时执行某些操作,现在建议使用该visibilitychange事件并检查从passivetohidden状态的转换

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>

有关 vanilla 页面生命周期事件的可靠性的更多数字(没有lifecycle.js),还有这个研究

MDN 说unload and beforeunload aren’t the right events to use with sendBeacon. Instead, use visibilitychange这是没用的developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon
2021-03-14 21:25:58
@AlfieRobles 如果您依赖unload事件获取重要数据,请检查对此答案的更改
2021-03-23 21:25:58
这个答案改变了我的生活!非常感谢!
2021-03-25 21:25:58
@godblessstrawberry 确实如此。MDN 恢复了他们unload最近使用的建议github.com/mdn/sprints/issues/3722我已经重写了答案以考虑到这一点。
2021-03-29 21:25:58

unloadbeforeunloadjavascript 事件,但这些对于 Ajax 请求是不可靠的(不能保证在这些事件之一中发起的请求会到达服务器)。

因此,强烈建议这样做,您应该寻找替代方法。

如果您确实需要这个,请考虑“ping”风格的解决方案。每分钟发送一个请求,基本上是告诉服务器“我还在这里”。然后,如果服务器超过两分钟没有收到这样的请求(你必须考虑延迟等),你认为客户端离线。


另一种解决方案是使用unloadbeforeunload执行 Sjax 请求(同步 JavaScript 和 XML),但完全不推荐这样做。这样做基本上会冻结用户的浏览器,直到请求完成,这是他们不会喜欢的(即使请求花费的时间很少)。

我认为问题是请求需要时间,浏览器可能会在完成时完成卸载(所以它可能永远不会完成)?
2021-03-12 21:25:58
我认为这将是您最好的选择(尽管如果您想 100% 确定,您仍然必须执行 ping 操作)。顺便说一句,这样做的方法是return "a string";在您的beforeunload处理程序中(这样做是confirm()行不通的)。
2021-03-17 21:25:58
好吧,如果他正在注销,那么我已经看到了一些工作示例,其中卸载会打开一个弹出窗口,然后将用户注销(这是否解决了非保证问题?)。
2021-03-19 21:25:58
不。我敢打赌,有一些弹出窗口阻止程序不允许在这些事件上弹出窗口。此外,当用户关闭选项卡/窗口时打开弹出窗口也很糟糕。
2021-03-26 21:25:58
我实际上正在做我还在这里的事情。但是有点慢并且降低了用户体验。同样减少“我还在这儿”的间隔可以以高用户率杀死服务器。所以我试图用别的东西代替它。当您说请求不可靠时,您的意思是多么不可靠?我可以使用它并以更高的时间间隔发送消息,成功率超过 50%。我认为这可以提高双方(服务器和客户端)的性能。
2021-03-27 21:25:58

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。

移动设备的重要信息:请注意,不保证为移动设备触发卸载事件处理程序但是visibilitychange事件肯定会被触发。因此,对于移动设备,您的数据收集代码可能需要进行一些调整。

3种方式的代码实现可以参考我的博客文章

我知道它存在,但不记得它的名字。这是正确的答案 IMO "navigator.sendBeacon" <--
2021-03-16 21:25:58
2018年,这是最好的回应。使用信标 API。阅读答案中提到的博客文章(usefulangle.com/post/62/...)了解更多详情。
2021-04-05 21:25:58

我也想实现相同的功能并从 Felix那里找到了这个答案(不能保证在这些事件之一中发起的请求会到达服务器)。

为了使请求到达服务器,我们尝试了以下代码:-

onbeforeunload = function() {

    //Your code goes here.

    return "";
} 

我们正在使用 IE 浏览器 & 现在当用户关闭浏览器时,他会收到确认对话框,因为return "";& 等待用户的确认 & 这个等待时间使请求到达服务器。

如果 beforeunload 函数中不止有 retrun 字符串语句,那么当前的 Chrome 似乎完全忽略了这一点。所以你可以返回“你想关闭吗?”,但你不能做任何其他事情......
2021-03-20 21:25:58

发布问题多年后,我做了一个更好的实现,包括 nodejs 和 socket.io ( https://socket.io )(你可以使用任何类型的套接字,但这是我个人的选择)。

基本上我打开与客户端的连接,当它挂断时,我只是保存数据/做我需要的任何事情。显然,这不能用于显示任何内容/重定向客户端(因为您在服务器端这样做),但当时我真正需要的是。

 io.on('connection', function(socket){
      socket.on('disconnect', function(){
          // Do stuff here
      });
 });

所以......现在我认为这会是一种比卸载版本。