How to detect if URL has changed after hash in JavaScript

330,128

Solution 1

In modern browsers (IE8+, FF3.6+, Chrome), you can just listen to the hashchange event on window.

In some old browsers, you need a timer that continually checks location.hash. If you're using jQuery, there is a plugin that does exactly that.

Example

Below I undo any URL change, to keep just the scrolling:

<script type="text/javascript">
  if (window.history) {
    var myOldUrl = window.location.href;
    window.addEventListener('hashchange', function(){
      window.history.pushState({}, null, myOldUrl);
    });
  }
</script>

Note that above used history-API is available in Chrome, Safari, Firefox 4+, and Internet Explorer 10pp4+

Solution 2

I wanted to be able to add locationchange event listeners. After the modification below, we'll be able to do it, like this

window.addEventListener('locationchange', function () {
    console.log('location changed!');
});

In contrast, window.addEventListener('hashchange',() => {}) would only fire if the part after a hashtag in a url changes, and window.addEventListener('popstate',() => {}) doesn't always work.

This modification, similar to Christian's answer, modifies the history object to add some functionality.

By default, before these modifications, there's a popstate event, but there are no events for pushstate, and replacestate.

This modifies these three functions so that all fire a custom locationchange event for you to use, and also pushstate and replacestate events if you want to use those.

These are the modifications:

(() => {
    let oldPushState = history.pushState;
    history.pushState = function pushState() {
        let ret = oldPushState.apply(this, arguments);
        window.dispatchEvent(new Event('pushstate'));
        window.dispatchEvent(new Event('locationchange'));
        return ret;
    };

    let oldReplaceState = history.replaceState;
    history.replaceState = function replaceState() {
        let ret = oldReplaceState.apply(this, arguments);
        window.dispatchEvent(new Event('replacestate'));
        window.dispatchEvent(new Event('locationchange'));
        return ret;
    };

    window.addEventListener('popstate', () => {
        window.dispatchEvent(new Event('locationchange'));
    });
})();

Note, we're creating a closure, to save the old function as part of the new one, so that it gets called whenever the new one is called.

Solution 3

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

window.onpopstate = function() { 
     //code  
}

or

window.addEventListener('hashchange', function() { 
  //code  
});

window.addEventListener('popstate', function() { 
  //code  
});

with jQuery

$(window).bind('hashchange', function() {
     //code
});

$(window).bind('popstate', function() {
     //code
});

Solution 4

EDIT after a bit of researching:

It somehow seems that I have been fooled by the documentation present on Mozilla docs. The popstate event (and its callback function onpopstate) are not triggered whenever the pushState() or replaceState() are called in code. Therefore the original answer does not apply in all cases.

However there is a way to circumvent this by monkey-patching the functions according to @alpha123:

var pushState = history.pushState;
history.pushState = function () {
    pushState.apply(history, arguments);
    fireEvents('pushState', arguments);  // Some event-handling function
};

Original answer

Given that the title of this question is "How to detect URL change" the answer, when you want to know when the full path changes (and not just the hash anchor), is that you can listen for the popstate event:

window.onpopstate = function(event) {
  console.log("location: " + document.location + ", state: " + JSON.stringify(event.state));
};

Reference for popstate in Mozilla Docs

Currently (Jan 2017) there is support for popstate from 92% of browsers worldwide.

Solution 5

With jquery (and a plug-in) you can do

$(window).bind('hashchange', function() {
 /* things */
});

http://benalman.com/projects/jquery-hashchange-plugin/

Otherwise yes, you would have to use setInterval and check for a change in the hash event (window.location.hash)

Update! A simple draft

function hashHandler(){
    this.oldHash = window.location.hash;
    this.Check;

    var that = this;
    var detect = function(){
        if(that.oldHash!=window.location.hash){
            alert("HASH CHANGED - new has" + window.location.hash);
            that.oldHash = window.location.hash;
        }
    };
    this.Check = setInterval(function(){ detect() }, 100);
}

var hashDetection = new hashHandler();
Share:
330,128

Related videos on Youtube

AJ00200
Author by

AJ00200

Coder's Love: &lt;#

Updated on April 01, 2022

Comments

  • AJ00200
    AJ00200 about 2 years

    How can I check if a URL has changed in JavaScript? For example, websites like GitHub, which use AJAX, will append page information after a # symbol to create a unique URL without reloading the page. What is the best way to detect if this URL changes?

    • Is the onload event called again?
    • Is there an event handler for the URL?
    • Or must the URL be checked every second to detect a change?
  • BergP
    BergP almost 11 years
    can I detect change of (window.location) and handle it? (without jquery)
  • NPC
    NPC about 10 years
    This, as I understand, works only for the change of the part after the # sign (hence the event name)? And not for full URL change, as seems to be implied by the question's title.
  • Ron
    Ron almost 10 years
    You can @BergP, Using the plain javascript listener: window.addEventListener("hashchange", hashChanged);
  • Hasib Mahmud
    Hasib Mahmud almost 10 years
    Is such short time interval good for the app? That is, doesn't it keep the browser too busy in executing detect() function?
  • Neha Choudhary
    Neha Choudhary over 9 years
    @NPC Any handler for full URL change(without anchor tag)?
  • Mark Bolusmjak
    Mark Bolusmjak over 9 years
    @HasibMahmud, that code is doing 1 equality check every 100ms. I just benchmarked in my browser that I can do 500 equality checks in under 1ms. So that code is using 1/50000th of my processing power. I wouldn't worry too much.
  • Deborah
    Deborah about 8 years
    Where content is Ajaxed in, the url may change without the window being unloaded. This script does not detect a url change, although it may still be helpful for some users who do have a window unload on every url change.
  • ncubica
    ncubica almost 8 years
    this will not work in the context of single page applications since the unload event will never trigger
  • ncubica
    ncubica almost 8 years
    same as @ranbuch question, this is specific only for pages that are not single page application, and this event is only watching the unload window event, not the url change.
  • Nick Mitchell
    Nick Mitchell over 7 years
    This must be marked the answer. It's one line, uses browser event model and doesn't rely on endless resource consuming timeouts
  • Neithan Max
    Neithan Max over 7 years
    This is a rather rudimentary method, I think we can aim higher.
  • Trev14
    Trev14 almost 7 years
    Although I agree with @CarlesAlcolea that this feels old, in my experience it is still the only way to catch 100% of all url changes.
  • NycCompSci
    NycCompSci over 6 years
    Doesn't work on non-hash url changes which seems to be very popular such as the one implemented by Slack
  • Anand Singh
    Anand Singh about 6 years
    It's for nodeJs, we need to use browserify to use it client-side. Don't we?
  • Ray Booysen
    Ray Booysen about 6 years
    No it isn't. Works in the browser
  • divibisan
    divibisan about 6 years
    @ahofmann suggests (in an edit that should have been a comment) changing setInterval to setTimeout: "using setInterval() will bring the Browser to a halt after a while, because it will create a new call to checkURLchange() every second. setTimeout() is the correct solition, because it is called only once."
  • ReturnTable
    ReturnTable about 6 years
    This has been the only solution to catch all the URL changes but the resource consuming is forcing me to seek some other solutions.
  • Aaron
    Aaron almost 6 years
    How is this the best answer if this is only triggered when there is a hash in the url?
  • goat
    goat over 5 years
    Unbelievable that we must still resort to such hacks in 2018.
  • wasddd_
    wasddd_ over 5 years
    This worked for my use case - but just like @goat says - it's unbelievable that there's no native support for this...
  • Rod Lima
    Rod Lima over 5 years
    You solution is simple and works very well for chrome extensions. I would like to suggest to use the YouTube video id instead of length. stackoverflow.com/a/3452617/808901
  • SeanMC
    SeanMC over 5 years
    what arguments? how would I set up fireEvents?
  • SeanMC
    SeanMC over 5 years
    pop state only triggers when you pop a state, not push one
  • eslamb
    eslamb over 5 years
    Great, what I needed Thanks
  • Stef Chäser
    Stef Chäser over 5 years
    you shouldn't use setInterval because each time you call listen(xy) a new Interval is created and you end up with thousands of intervals.
  • Alburkerk
    Alburkerk over 5 years
    You are right I noticed that after and didn't change my post, I will edit that. Back in the time I even encountered a crash of Google Chrome because of RAM leaks. Thank you for the comment
  • thdoan
    thdoan about 5 years
    Note that this is also unreliable in many cases. For example, it won't detect the URL change when you click on different Amazon product variations (the tiles underneath the price).
  • Sjeiti
    Sjeiti almost 5 years
    You rarely need timeout events: use mouse- and keyboardevents for checking.
  • joshuascotton
    joshuascotton almost 5 years
    Is there a way to do this in IE? As it doesn't support =>
  • Thomas Lang
    Thomas Lang almost 5 years
    @joshuascotton yes there is! I'll try and add it in the answer here
  • aljgom
    aljgom almost 5 years
    @joshuacotton => is an arrow function, you can replace f => function fname(){...} with function(f){ return function fname(){...} }
  • aristidesfl
    aristidesfl almost 5 years
    Or instead of using setTimeout like @divibisan suggests, move the setInterval outside of the function. checkURLchange(); also becomes optional.
  • Kunal Parekh
    Kunal Parekh over 4 years
    This is very helpful and works like a charm. This should be the accepted answer.
  • SuperUberDuper
    SuperUberDuper over 4 years
    what if the path changes, not the hash?
  • SuperUberDuper
    SuperUberDuper over 4 years
    this wont detect a change from localhost/foo to localhost/baa if not using location.back()
  • phihag
    phihag over 4 years
    @SuperUberDuper If the path changes because the user initiated a navigation / clicked a link etc., then you will only see a beforeunload event. If your code initiated the URL change, it knows best.
  • Diego Fortes
    Diego Fortes over 4 years
    I agree that from all the answers this was the only way I was able to catch all of the URL changes.
  • Haritsinh Gohil
    Haritsinh Gohil over 4 years
    Your example works perfectly, all others on internet suggesting to use hashchange, but i don't use hash in my url, i just want to add listener for url change, thanks for sharing.
  • Collin Krawll
    Collin Krawll over 4 years
    window.history has a max length of 50 (at least as of Chrome 80). After that point, window.history.length always returns 50. When that happens, this method will fail to recognize any changes.
  • broken-e
    broken-e over 4 years
    You should be using addEventListener instead of replacing the onhashchange value directly, in case something else wants to listen as well.
  • Alberto S.
    Alberto S. over 4 years
    what about if you want to monitor any new or update on the url GET params? thanks
  • JulienD
    JulienD over 4 years
    Doesn't it send 'locationchange' twice then? Once when we fire it here, and once when the URL changes?
  • aljgom
    aljgom about 4 years
    @JulienD 'locationchange' is a custom event, it doesn't exist without this code. It doesn't get fired by default with a url change without this modification
  • Operator
    Operator almost 4 years
    This only works when navigating with the browsers back and forward buttons, ie completely useless in many cases.
  • Seph Reed
    Seph Reed over 3 years
    This didn't work for my project. It comes pre-bundled and makes a lot of assumptions about what bundler you are using.
  • Gel
    Gel over 3 years
    whats fireEvents ?
  • ajax333221
    ajax333221 almost 3 years
    you can get rid of globals function checkURLchange(old_url){ var temp; temp=window.location.href; if(temp!=old_url){ fireChangeToBoard(); } old_url=temp; setTimeout(function(){ checkURLchange(old_url); }, 1000); }
  • aljgom
    aljgom over 2 years
    The code you wrote for the function callbacks is essentially what addEventListener and dispatchEvent do. You save callbacks using addEventListener(eventType, function), and when you dispatch an event, all the functions get called
  • Luis Lobo
    Luis Lobo over 2 years
    If I was going to use something today for routes I would use crossroads.js
  • Monday Fatigue
    Monday Fatigue over 2 years
    pageshow for user activated history navigation
  • Kevin Farrugia
    Kevin Farrugia over 2 years
    Unbelievable that we must resort to such hacks in 2022.
  • Isaac Weingarten
    Isaac Weingarten about 2 years
    I have a spa svelte with html5 routing this answer was the only answer that worked for me, because html5 routing is not hash-based routing so the hash event didn't work in my case, thank you
  • szaman
    szaman about 2 years
    is this seriously the 2022 answer? ugh
  • Maciej Krawczyk
    Maciej Krawczyk about 2 years
    If you have full control of the app it would be better to add a custom pushState function that does it and call it instead, rather than modifying the native function.