How to Detect Browser Back Button event - Cross Browser

702,628

Solution 1

(Note: As per Sharky's feedback, I've included code to detect backspaces)

So, I've seen these questions frequently on SO, and have recently run into the issue of controlling back button functionality myself. After a few days of searching for the best solution for my application (Single-Page with Hash Navigation), I've come up with a simple, cross-browser, library-less system for detecting the back button.

Most people recommend using:

window.onhashchange = function() {
 //blah blah blah
}

However, this function will also be called when a user uses on in-page element that changes the location hash. Not the best user experience when your user clicks and the page goes backwards or forwards.

To give you a general outline of my system, I'm filling up an array with previous hashes as my user moves through the interface. It looks something like this:

function updateHistory(curr) {
    window.location.lasthash.push(window.location.hash);
    window.location.hash = curr;
}

Pretty straight forward. I do this to ensure cross-browser support, as well as support for older browsers. Simply pass the new hash to the function, and it'll store it for you and then change the hash (which is then put into the browser's history).

I also utilise an in-page back button that moves the user between pages using the lasthash array. It looks like this:

function goBack() {
    window.location.hash = window.location.lasthash[window.location.lasthash.length-1];
    //blah blah blah
    window.location.lasthash.pop();
}

So this will move the user back to the last hash, and remove that last hash from the array (I have no forward button right now).

So. How do I detect whether or not a user has used my in-page back button, or the browser button?

At first I looked at window.onbeforeunload, but to no avail - that is only called if the user is going to change pages. This does not happen in a single-page-application using hash navigation.

So, after some more digging, I saw recommendations for trying to set a flag variable. The issue with this in my case, is that I would try to set it, but as everything is asynchronous, it wouldn't always be set in time for the if statement in the hash change. .onMouseDown wasn't always called in click, and adding it to an onclick wouldn't ever trigger it fast enough.

This is when I started to look at the difference between document, and window. My final solution was to set the flag using document.onmouseover, and disable it using document.onmouseleave.

What happens is that while the user's mouse is inside the document area (read: the rendered page, but excluding the browser frame), my boolean is set to true. As soon as the mouse leaves the document area, the boolean flips to false.

This way, I can change my window.onhashchange to:

window.onhashchange = function() {
    if (window.innerDocClick) {
        window.innerDocClick = false;
    } else {
        if (window.location.hash != '#undefined') {
            goBack();
        } else {
            history.pushState("", document.title, window.location.pathname);
            location.reload();
        }
    }
}

You'll note the check for #undefined. This is because if there is no history available in my array, it returns undefined. I use this to ask the user if they want to leave using a window.onbeforeunload event.

So, in short, and for people that aren't necessarily using an in-page back button or an array to store the history:

document.onmouseover = function() {
    //User's mouse is inside the page.
    window.innerDocClick = true;
}

document.onmouseleave = function() {
    //User's mouse has left the page.
    window.innerDocClick = false;
}

window.onhashchange = function() {
    if (window.innerDocClick) {
        //Your own in-page mechanism triggered the hash change
    } else {
        //Browser back button was clicked
    }
}

And there you have it. a simple, three-part way to detect back button usage vs in-page elements with regards to hash navigation.

EDIT:

To ensure that the user doesn't use backspace to trigger the back event, you can also include the following (Thanks to @thetoolman on this Question):

$(function(){
    /*
     * this swallows backspace keys on any non-input element.
     * stops backspace -> back
     */
    var rx = /INPUT|SELECT|TEXTAREA/i;

    $(document).bind("keydown keypress", function(e){
        if( e.which == 8 ){ // 8 == backspace
            if(!rx.test(e.target.tagName) || e.target.disabled || e.target.readOnly ){
                e.preventDefault();
            }
        }
    });
});

Solution 2

You can try popstate event handler, e.g:

window.addEventListener('popstate', function(event) {
    // The popstate event is fired each time when the current history entry changes.

    var r = confirm("You pressed a Back button! Are you sure?!");

    if (r == true) {
        // Call Back button programmatically as per user confirmation.
        history.back();
        // Uncomment below line to redirect to the previous page instead.
        // window.location = document.referrer // Note: IE11 is not supporting this.
    } else {
        // Stay on the current page.
        history.pushState(null, null, window.location.pathname);
    }

    history.pushState(null, null, window.location.pathname);

}, false);

Note: For the best results, you should load this code only on specific pages where you want to implement the logic to avoid any other unexpected issues.

The popstate event is fired each time when the current history entry changes (user navigates to a new state). That happens when user clicks on browser's Back/Forward buttons or when history.back(), history.forward(), history.go() methods are programatically called.

The event.state is property of the event is equal to the history state object.

For jQuery syntax, wrap it around (to add even listener after document is ready):

(function($) {
  // Above code here.
})(jQuery);

See also: window.onpopstate on page load


See also the examples on Single-Page Apps and HTML5 pushState page:

<script>
// jQuery
$(window).on('popstate', function (e) {
    var state = e.originalEvent.state;
    if (state !== null) {
        //load content with ajax
    }
});

// Vanilla javascript
window.addEventListener('popstate', function (e) {
    var state = e.state;
    if (state !== null) {
        //load content with ajax
    }
});
</script>

This should be compatible with Chrome 5+, Firefox 4+, IE 10+, Safari 6+, Opera 11.5+ and similar.

Solution 3

I had been struggling with this requirement for quite a while and took some of the solutions above to implement it. However, I stumbled upon an observation and it seems to work across Chrome, Firefox and Safari browsers + Android and iPhone

On page load:

window.history.pushState({page: 1}, "", "");

window.onpopstate = function(event) {

  // "event" object seems to contain value only when the back button is clicked
  // and if the pop state event fires due to clicks on a button
  // or a link it comes up as "undefined" 

  if(event){
    // Code to handle back button or prevent from navigation
  }
  else{
    // Continue user action through link or button
  }
}

Let me know if this helps. If am missing something, I will be happy to understand.

Solution 4

In javascript, navigation type 2 means browser's back or forward button clicked and the browser is actually taking content from cache.

if(performance.navigation.type == 2)
{
    //Do your code here
}

Solution 5

if (window.performance && window.performance.navigation.type == window.performance.navigation.TYPE_BACK_FORWARD) {
  alert('hello world');
}

This is the only one solution that worked for me (it's not a onepage website). It's working with Chrome, Firefox and Safari.

Share:
702,628

Related videos on Youtube

Xarus
Author by

Xarus

Technical Manager and Head of R&amp;D at GameBench Labs in London. Strong knowledge and experience with Angular, NodeJS, HTML5, CSS3, Python, Perl, GoLang, ML (TensorFlow), mobile performance testing and methods

Updated on January 25, 2022

Comments

  • Xarus
    Xarus over 2 years

    How do you definitively detect whether or not the user has pressed the back button in the browser?

    How do you enforce the use of an in-page back button inside a single page web application using a #URL system?

    Why on earth don't browser back buttons fire their own events!?

  • Sharky
    Sharky almost 10 years
    +1 nice idea, but i think this will fail if user uses whatever keyboard shortcut is for "back" (backspace key on firefox) whilst his mouse is inside browser window
  • Xarus
    Xarus almost 10 years
    Agreed, but you can intercept the onkey event and check for backspace anyways. The issue has always been detecting the click of the back button. :)
  • Sharky
    Sharky almost 10 years
    you should implement this too, its a pity to leave it like this :D
  • Xarus
    Xarus almost 10 years
    Will do - I'm in the office right now anyways :) (EDIT: Done now)
  • basarat
    basarat over 9 years
    What about mobile (e.g. ipad)
  • Xarus
    Xarus over 9 years
    @basarat - I haven't tested on an iPad or other mobile devices - but given how there are no mouse events on touch devices - I don't believe this is mobile friendly - though I'll spend some time looking into a way to adapt it. :)
  • Sriganesh Navaneethakrishnan
    Sriganesh Navaneethakrishnan almost 9 years
    What about swipe events from trackpad in MAC. Could that be captured as well?
  • Xarus
    Xarus almost 9 years
    @SriganeshNavaneethakrishnan - I'll need to look into that as well, but I don't have a trackpad or mac at my disposal.
  • Xarus
    Xarus about 8 years
    Kenorb, you're correct that popstate works to grab the change, but it doesn't differentiate between programmatically calling the event and when the user clicks the browser's buttons.
  • lowtechsun
    lowtechsun about 8 years
    Great post on Single-Page Apps and HTML5 pushState. Perfect for modern "one page layouts" or "single page apps". Thank you for the link and your answer!
  • Xarus
    Xarus over 7 years
    This doesn't answer the question asked. The question is about detecting the use of an in-page back button vs the browser's native back button.
  • Mr_Perfect
    Mr_Perfect over 7 years
    How can I differentiate forward and backward buttons?
  • skwny
    skwny over 7 years
    Question is tagged javascript, not jquery.
  • James Wong
    James Wong almost 7 years
    @MunamYousuf It's pretty obvious why this piece of code doesn't really work. It tracks the back button of the browser but the button is outside the viewport, hence the coordinates clientX and clientY should not be obtainable. Assuming it works, how about iOS? The back button is at the bottom... Also the way it is written is super inefficient, it is a conditional for every single event triggered... God it's so bad that am going to give it a down vote.
  • a11r
    a11r over 6 years
    e.state seem always undefined in modern browsers
  • msqar
    msqar over 6 years
    This is horrendous! why would you do such thing? :/ and it won't even work as James said.
  • Daniel Birowsky Popeski
    Daniel Birowsky Popeski about 6 years
    Not true. event has value even for the forward button
  • FourCinnamon0
    FourCinnamon0 about 6 years
    I suggest that you put your code in a snippet so that you can run it directly here.
  • Ashwin
    Ashwin almost 6 years
    Above code worked for me , on mobile browsers, when users clicked on back button , we wanted to restore the page state as per his previous visit
  • Tom
    Tom over 5 years
    UPDATE: window.onhashchange is not called in latest Chrome (69) when I click back button.
  • papillon
    papillon over 5 years
    This doesn't work on a single page application, the result is always 1 (which means reload). Moreover it doesn't make the difference between back and forward button, which is very unfortunate.
  • Hasan Badshah
    Hasan Badshah over 5 years
    " Moreover it doesn't make the difference between back and forward button, which is very unfortunate. " yes because whenever data is fetched from cache that time the performance.navigation.type is 2
  • SpaceManGalaxy
    SpaceManGalaxy over 5 years
    UPDATE: Tom, it is called in Chrome (72) but ONLY if you arrive at that page/hash from another page/hash (maybe the same in 69?). If you hit reload and then press the back button it is NOT called. The same is true for popstate. If anyone finds a solution to catching the back button immediately after a reload then please let me know. :)
  • blank94
    blank94 about 5 years
    That's not guaranteed to work in all browsers! so you should write something like this : if(window.performance){ // your performance related code } also it's better to use TYPE_BACK_FORWARD instead of 2 for more readability.
  • Howdy
    Howdy about 5 years
    window.performance.navigation has been deprecated. Here are the docs developer.mozilla.org/en-US/docs/Web/API/Performance/navigat‌​ion
  • davidhartman00
    davidhartman00 over 4 years
    document.onmouseleave does not work in FireFox (ver 69. but did in Safari and Chrome). Nor does document.addEventListener('mouseleave', ..... Instead I opted for document.getElementsByTagName('body')[0].addEventListener('m‌​ouseleave', ... Not as pretty yet does the trick.
  • Jeffz
    Jeffz over 4 years
    Just tried that on Chrome 78.0.3904.70 (Official Build) (64-bit) and it did not work.
  • Jeffz
    Jeffz over 4 years
    Does not work in Chrome 78.0.3904.70 (Official Build) (64-bit)
  • Seph Reed
    Seph Reed over 4 years
    This is not a way to tell if a back button is clicked on this page. It tells if it was clicked on the last page.
  • Xarus
    Xarus over 4 years
    Nice approach! Thanks for adding to this (I still find it amazing that there's conversation on this thread after so many years)
  • ZeroThe2nd
    ZeroThe2nd over 4 years
    Confirmed working on Brave v1.3.118 >> Chromium: 80.0.3987.116 (Official Build) (64-bit)
  • Andrew
    Andrew over 4 years
    But it happened to be the actual answer I was looking for since the issue I get is after I hit the back button and not before meaning I can build in some response code that stops my duplicate requests issues. Thanks.
  • Marketa Vlach
    Marketa Vlach over 4 years
    Doesn't work if you came on the page from an external site.
  • Justjyde
    Justjyde about 4 years
    This worked for me on my .cshtml page. Worked successfully on Chrome and IE. Thanks
  • CubicleSoft
    CubicleSoft about 4 years
    This appears to be a clever history tracking mechanism. However, the code is uncommented, so it's pretty difficult to follow. If someone navigates away from the current page, the code won't detect that button press, which doesn't answer the original question of "How do you definitively detect whether or not the user has pressed the back button in the browser?" Button presses and history tracking are two different things even though one may trigger the other. The fact there's still conversation indicates a major flaw in web browser design.
  • Hogsmill
    Hogsmill about 4 years
    This may not answer the question, but exactly what I needed! Nice one - I did not know of this...
  • CubicleSoft
    CubicleSoft about 4 years
    @Mr_Perfect See a possible solution here if you want both forward and back buttons: stackoverflow.com/questions/57102502/…
  • AlexeyP0708
    AlexeyP0708 almost 4 years
    CubicleSoft, Let’s imagine that there is an actual transition (a page formed directly when the link is clicked) and a virtual transition (a page generated by the event, while there is no real click through). In the actual transition, (if the domains are different during the transition), the browser does not allow you to track the transitions due to security policies and privacy. With a dynamic transition, your code is executed in the context of your actions, you have the opportunity to track the actions in the navigation panel. At the very least, this is satisfactory for your SPA application.
  • Mister Vanderbilt
    Mister Vanderbilt over 3 years
  • Mladen Adamovic
    Mladen Adamovic over 3 years
    @MisterVanderbilt as this is deprecated, what would be the new alternative code. This solution is so simple, would like to use it
  • Mladen Adamovic
    Mladen Adamovic over 3 years
    @llaaalu This works after the deprecation, I'm not sure about cross browser compatibility: if (String(window.performance.getEntriesByType("navigation")[0]‌​.type) === "back_forward") { // do your code here }
  • Mladen Adamovic
    Mladen Adamovic over 3 years
    This works after the deprecation, I'm not sure about cross browser compatibility: if (String(window.performance.getEntriesByType("navigation")[0]‌​.type) === "back_forward") { // do your code here }
  • toastal
    toastal over 3 years
    There's no reason to store the state in the DOM. Nor should you be requerying the DOM for the input and it should be stored as a local variable. If you don't want an <input> to be displayed, use <input type="hidden">. If you wanted to use this approach, a variable on the window would be preferred, but even that isn't wise.
  • Cătălin Rădoi
    Cătălin Rădoi over 3 years
    I don't care. It works for me, is good! Dec 2020
  • Constantine Westerink
    Constantine Westerink about 3 years
    Is there a way to tell if user pressed cancel or leave page
  • maverabil
    maverabil about 3 years
    This helped me. Thanks!
  • pref
    pref almost 3 years
    The popstate event is only triggered by performing a browser action, such as clicking on the back button (or calling history.back() in JavaScript), when navigating between two history entries for the same document. ( developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers‌​/… )
  • Eduardo La Hoz Miranda
    Eduardo La Hoz Miranda over 2 years
    this is funny AF, tho XD
  • LuckyLuke Skywalker
    LuckyLuke Skywalker about 2 years
    what is the difference to the popstate event and are there even more possible options that are not onpageleave and unload?