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

我如何查看用户何时关闭我的页面(捕获window.close事件),然后……没什么关系(我需要发送AJAX请求) ,但如果我可以运行警报,则可以执行其余操作。)

#1 楼

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

因此,这样做是强烈建议您不要使用它,而应该寻找替代方法。

如果您确实需要此方法,请考虑使用“ ping”式的解决方案。每分钟发送一次请求,基本上告诉服务器“我还在这里”。然后,如果服务器超过两分钟没有收到这样的请求(您必须考虑延迟等),则认为客户端处于脱机状态。


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

评论


好吧,如果他正在注销,那么我已经看到了一些工作示例,其中卸载打开了一个弹出窗口,然后将用户注销(这解决了非担保人问题吗?)。

–乔纳森
2011年5月28日下午14:34

不,我敢打赌那里有弹出窗口阻止程序,这些事件不允许弹出窗口。同样,当用户关闭标签页/窗口时,打开一个弹出窗口也很糟糕。

–费利克斯
2011年5月28日下午14:36

我认为问题在于请求需要时间,并且浏览器可能会在完成时完成卸载(因此可能永远无法完成)?

–乔纳森
2011年5月28日下午14:36

我实际上正在做我仍然在这里的事情。但是有点慢并且减少用户体验。同样,减少“我仍然在这里”的时间间隔可能会以很高的用户率杀死服务器。所以我试图用其他东西代替它。当您说请求不可靠时,您的意思不可靠?我可以使用它,并以更高的间隔发送消息将其加倍,成功率超过50%。我认为这可以提高双方(服务器和客户端)的性能。

– zozo
2011年5月28日下午14:38

我认为这将是您最好的选择(尽管如果您想100%确定,则仍然必须执行ping操作)。顺便说一句,执行此操作的方法是返回“字符串”;在您的beforeunload处理程序中(执行Confirm()无效)。

–费利克斯
2011年5月29日19:58

#2 楼


要弄清楚,在2018年(直到2020年今天),Beacon API是解决此问题的方法(几乎在所有浏览器上)

Beacon保证请求可以在页面卸载之前完成,
它们可以完成运行,而无需阻止请求。

您可以在onunload事件中使用它,并确信它会已发送。
$(window).on('unload', function() {
    
    var URL = "https://example.com/foo";
    var data = "bar";

    navigator.sendBeacon(URL, data);

});


评论


这个答案改变了我的生活!非常感谢!

– Alfie Robles
19/12/17在21:20

MDN说,卸载和beforeunload不是与sendBeacon一起使用的正确事件。而是使用可视性更改,这样对developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon毫无用处

– godblessstrawberry
12月4日21:10



#3 楼

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。

对移动设备的重要提示:请注意,不能保证为移动设备触发卸载事件处理程序。但是可以保证能见度改变事件被触发。因此对于移动设备,您的数据收集代码可能需要一些调整。

您可以参考我的博客文章,了解这三种方式的代码实现。

评论


在2018年,这是最好的回应。使用信标API。阅读答案中提到的博客文章(有用的角度.com / post / 62 /…),以获取更多详细信息。

–白树
18-2-24在8:20



#4 楼

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

使请求到达我们在以下代码中尝试过的服务器:-

onbeforeunload = function() {

    //Your code goes here.

    return "";
} 


我们正在使用IE浏览器,现在当用户关闭浏览器时,由于return "";,他将收到确认对话框并等待用户确认&等待时间使请求到达服务器。

评论


如果在beforeunload函数中不仅有retrun字符串语句,当前的Chrome似乎完全忽略了这一点。因此,您可以返回“您要关闭吗?”,但是您不能做其他任何事情……

–米哈尔(Michal Politzer)
17年3月30日在8:01

#5 楼

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

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

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


所以。 ..如今,我认为与卸载版本相比,这将是一种更好的方法(尽管由于您需要节点,套接字等而难以实现,但并不难;如果您是第一次使用,则大约需要30分钟) 。

#6 楼

所选答案是正确的,您不能保证浏览器会发送xhr请求,但是根据浏览器的不同,您可以可靠地在选项卡或窗口关闭时发送请求。

通常,浏览器会关闭在xhr.send()实际执行之前。 Chrome和Edge看起来像它们在关闭窗口之前等待javascript事件循环清空。它们还会在与javascript事件循环不同的线程中触发xhr请求。这意味着,如果您可以将事件循环保持足够长的时间,则xhr将成功触发。例如,我测试了发送xhr请求,然后计数为100,000,000。这对我来说在Chrome和Edge上都非常稳定。如果您使用的是angularjs,则将对$ http的调用包装到$ apply中会完成相同的操作。

IE似乎有些不同。我不认为IE等待事件循环为空,甚至不等待当前的堆栈框架为空。尽管它偶尔会正确发送请求,但似乎更经常发生(80%-90%的时间)的是,IE会在xhr请求完全执行之前关闭窗口或选项卡,这只会导致部分消息正在发送。基本上,服务器接收到一个发布请求,但是没有任何内容。

为了后代,这是我用作窗口的代码。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

Edge 40.15063.0.0

评论


for循环所花费的时间将根据CPU,JS引擎和其他因素而有所不同。稍差一点的想法是将事件循环阻塞100ms或进行实验后认为足够长的任何其他值

–格雷格
17年11月1日在1:25

#7 楼


试试这个。我用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
   });
}


评论


您好,欢迎来到。不降低投票权(因为您是新手:)),但是您的答案对接受的答案没有什么新的影响。另外,如果我正确阅读它,则每当用户将鼠标移到窗口外时,它将触发ConfirmLeave函数,从而导致虚假请求(例如,每次将鼠标移到任务栏上或其他位置时)。此外,它不处理任何触摸/移动情况(因为事件仅在mouseOut上绑定,在事件上移除,并且从不绑定)。还要考虑简单地按f5的情况(在您的示例中也不起作用)。

– zozo
19-09-5在17:48



在2011年(当我发布问题时),这是不可能的(或者至少不像今天这样简单),但是如今,您可以为此使用websocket,这基本上为您带来了ping样式解决方案的优点而没有缺点。如果您想查看有关该内容的信息,我会很乐意赞成这样的答案(我正在考虑自己张贴一个,但是...变得懒惰了:))。

– zozo
19年9月5日于17:50

#8 楼

我同意Felix的想法,并且已经使用该解决方案解决了我的问题,现在我想清除服务器端解决方案:

1.将请求从客户端发送到服务器

2。保存上一个变量中收到的最后一个请求的时间

3.检查服务器时间并与上一个收到的变量中的变量进行比较


4 。如果结果超出预期的时间,请开始运行要在Windows关闭时运行的
代码...

#9 楼

使用:

<body onUnload="javascript:">


它应该捕获除关闭浏览器程序以外的所有内容。

评论


嗯(-1),我很想知道这种方法有什么问题。我自己从未使用过它,但是已经多次遇到它作为您确切问题的解决方案。

–乔纳森
2011年5月28日14:30

我已经发布了一个答案,解释了-1。

–费利克斯
2011年5月28日下午14:32