229

What is the best way to detect if a user leaves a web page?

The onunload JavaScript event doesn't work every time (the HTTP request takes longer than the time required to terminate the browser).

Creating one will probably be blocked by current browsers.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ANaimi
  • 6,266
  • 7
  • 32
  • 32

11 Answers11

239

Try the onbeforeunload event: It is fired just before the page is unloaded. It also allows you to ask back if the user really wants to leave. See the demo onbeforeunload Demo.

Alternatively, you can send out an Ajax request when he leaves.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Andreas Petersson
  • 16,248
  • 11
  • 59
  • 91
  • 3
    It'd be nice for the user if you tracked if the form changed and only prompt if it did - so it's not annoying. – adam0101 Sep 10 '10 at 21:13
  • 3
    Note also that various mobile browsers ignore the result of the event (that is, they do not ask the user for confirmation). Firefox has a hidden preference in about:config to do the same. In essence this means the user always confirms that the document may be unloaded. – MJB Jan 19 '15 at 17:29
  • 2
    Note that this method also evokes when user refresh page or submit a form. – Logovskii Dmitrii Jan 20 '18 at 19:04
  • @Andreas Petersson, I have the same challenge with the objective to drop user session. How do i could capture the url typed to check if that belongs to the project? – Jcc.Sanabria Jun 19 '18 at 22:31
33

Mozilla Developer Network has a nice description and example of onbeforeunload.

If you want to warn the user before leaving the page if your page is dirty (i.e. if user has entered some data):

window.addEventListener('beforeunload', function(e) {
  var myPageIsDirty = ...; //you implement this logic...
  if(myPageIsDirty) {
    //following two lines will cause the browser to ask the user if they
    //want to leave. The text of this dialog is controlled by the browser.
    e.preventDefault(); //per the standard
    e.returnValue = ''; //required for Chrome
  }
  //else: user is allowed to leave without a warning dialog
});
Kip
  • 107,154
  • 87
  • 232
  • 265
DuckMaestro
  • 15,232
  • 11
  • 67
  • 85
16

Here's an alternative solution - since in most browsers the navigation controls (the nav bar, tabs, etc.) are located above the page content area, you can detect the mouse pointer leaving the page via the top and display a "before you leave" dialog. It's completely unobtrusive and it allows you to interact with the user before they actually perform the action to leave.

$(document).bind("mouseleave", function(e) {
    if (e.pageY - $(window).scrollTop() <= 1) {    
        $('#BeforeYouLeaveDiv').show();
    }
});

The downside is that of course it's a guess that the user actually intends to leave, but in the vast majority of cases it's correct.

rustyx
  • 80,671
  • 25
  • 200
  • 267
  • i am looking something like, i want to show something unobtrusive to my user, before leaving my page, i am looking something like a hover event on back button, because i need to fire the event before the user clicks to go back. I believe that the best way is really detecting the mouse leave body document, and do some logic to determine if the user interacted with the page. – Leonardo Souza Paiva Aug 09 '19 at 19:05
9

In the case you need to do some asynchronous code (like sending a message to the server that the user is not focused on your page right now), the event beforeunload will not give time to the async code to run. In the case of async I found that the visibilitychange and mouseleave events are the best options. These events fire when the user change tab, or hiding the browser, or taking the courser out of the window scope.

document.addEventListener('mouseleave', e=>{
     //do some async code
})

document.addEventListener('visibilitychange', e=>{
     if (document.visibilityState === 'visible') {
   //report that user is in focus
    } else {
     //report that user is out of focus
    }  
})
Tal Yaron
  • 355
  • 2
  • 12
4

I know this question has been answered, but in case you only want something to trigger when the actual BROWSER is closed, and not just when a pageload occurs, you can use this code:

window.onbeforeunload = function (e) {
        if ((window.event.clientY < 0)) {
            //window.localStorage.clear();
            //alert("Y coords: " + window.event.clientY)
        }
};

In my example, I am clearing local storage and alerting the user with the mouses y coords, only when the browser is closed, this will be ignored on all page loads from within the program.

Merr Leader
  • 745
  • 2
  • 10
  • 13
  • window.event is undefined for me – Karl Glaser Aug 15 '14 at 04:30
  • 1
    Merr Leader's example is wrong, window.event should only be used as a fallback for example for older versions of IE, in other cases, the event parameter (the `e` variable in this case) should be used: http://stackoverflow.com/questions/9813445/why-ff-says-that-window-event-is-undefined-call-function-with-added-event-list/9813624#9813624 – Sk8erPeter Jan 13 '15 at 13:47
  • Wouldn't go as far as to say I'm wrong. My examples works for me in IE and Chrome just fine. My example was for the scenario that the user clicks the X(close) on the web browser. Nor the prettiest way maybe, but it does work. – Merr Leader Jan 13 '15 at 20:57
  • 1
    From MDN: `This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user.` https://developer.mozilla.org/en-US/docs/Web/API/Window/event – Robin Neal Jan 20 '17 at 13:37
4

Thanks to Service Workers, it is possible to implement a solution similar to Adam's purely on the client-side, granted the browser supports it. Just circumvent heartbeat requests:

// The delay should be longer than the heartbeat by a significant enough amount that there won't be false positives
const liveTimeoutDelay = 10000
let liveTimeout = null

global.self.addEventListener('fetch', event => {
  clearTimeout(liveTimeout)
  liveTimeout = setTimeout(() => {
    console.log('User left page')
    // handle page leave
  }, liveTimeoutDelay)
  // Forward any events except for hearbeat events
  if (event.request.url.endsWith('/heartbeat')) {
    event.respondWith(
      new global.Response('Still here')
    )
  }
})
Jeffrey Sweeney
  • 5,986
  • 5
  • 24
  • 32
3

Page Visibility API

The Page Visibility API provides events which can be watch to know when a document becomes visible or hidden.

When the user minimizes the window or switches to another tab, API triggers a visibilitychange event.

We can perform the actions based on the visibilityState

function onVisibilityChange() {
  if (document.visibilityState === 'visible') {
     console.log("user is focused on the page")
  } else {
     console.log("user left the page")
  }
}

document.addEventListener('visibilitychange', onVisibilityChange);
Debug Diva
  • 26,058
  • 13
  • 70
  • 123
2

One (slightly hacky) way to do it is replace and links that lead away from your site with an AJAX call to the server-side, indicating the user is leaving, then use that same javascript block to take the user to the external site they've requested.

Of course this won't work if the user simply closes the browser window or types in a new URL.

To get around that, you'd potentially need to use Javascript's setTimeout() on the page, making an AJAX call every few seconds (depending on how quickly you want to know if the user has left).

Steve M
  • 10,517
  • 12
  • 52
  • 63
1

What you can do, is open up a WebSocket connection when the page loads, optionally send data through the WebSocket identifying the current user, and check when that connection is closed on the server.

luek baja
  • 1,475
  • 8
  • 20
1

A lot of people finding this question may be looking for a way to detect when the user is leaving the page without triggering a popup (which is the case for onbeforeunload event). If that's you, then check out the Beacon API.

In that case, it is not advisable to call an ajax function, because browsers will not guarantee that the request completes properly... Here's the relevant passage from the MDN documentation :

The main use case for the Beacon API is to send analytics such as client-side events or session data to the server. Historically, websites have used XMLHttpRequest for this, but browsers do not guarantee to send these asynchronous requests in some circumstances (for example, if the page is about to be unloaded). To combat this, websites have resorted to various techniques, such as making the request synchronous, that have a bad effect on responsiveness. Because beacon requests are both asynchronous and guaranteed to be sent, they combine good performance characteristics and reliability.

nameofname
  • 91
  • 5
-1

For What its worth, this is what I did and maybe it can help others even though the article is old.

PHP:

session_start();

$_SESSION['ipaddress'] = $_SERVER['REMOTE_ADDR'];

if(isset($_SESSION['userID'])){
    if(!strpos($_SESSION['activeID'], '-')){
        $_SESSION['activeID'] = $_SESSION['userID'].'-'.$_SESSION['activeID'];
    }
}elseif(!isset($_SESSION['activeID'])){
    $_SESSION['activeID'] = time();
}

JS

window.setInterval(function(){
            var userid = '<?php echo $_SESSION['activeID']; ?>';
            var ipaddress = '<?php echo $_SESSION['ipaddress']; ?>';
            var action = 'data';

            $.ajax({
                url:'activeUser.php',
                method:'POST',
                data:{action:action,userid:userid,ipaddress:ipaddress},
                success:function(response){
                     //alert(response);                 
                }
            });
          }, 5000);

Ajax call to activeUser.php

if(isset($_POST['action'])){
    if(isset($_POST['userid'])){
        $stamp = time();
        $activeid = $_POST['userid'];
        $ip = $_POST['ipaddress'];

        $query = "SELECT stamp FROM activeusers WHERE activeid = '".$activeid."' LIMIT 1";
        $results = RUNSIMPLEDB($query);

        if($results->num_rows > 0){
            $query = "UPDATE activeusers SET stamp = '$stamp' WHERE activeid = '".$activeid."' AND ip = '$ip' LIMIT 1";
            RUNSIMPLEDB($query);
        }else{
            $query = "INSERT INTO activeusers (activeid,stamp,ip)
                    VALUES ('".$activeid."','$stamp','$ip')";
            RUNSIMPLEDB($query);
        }
    }
}

Database:

CREATE TABLE `activeusers` (
  `id` int(11) NOT NULL,
  `activeid` varchar(20) NOT NULL,
  `stamp` int(11) NOT NULL,
  `ip` text
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Basically every 5 seconds the js will post to a php file that will track the user and the users ip address. Active users are simply a database record that have an update to the database time stamp within 5 seconds. Old users stop updating to the database. The ip address is used just to ensure that a user is unique so 2 people on the site at the same time don't register as 1 user.

Probably not the most efficient solution but it does the job.

Adam
  • 35
  • 2