Change URL hash without page jump

In modern complex layouts, sometimes the point where a hash will transport you to will be entirely different than the one you actually wanted. If you prevent the default event, you will save yourself from the page jump, but the hash won’t change either. You can accept the regular behavior and change scrollTop after the jump, but the user will still see a distracting flicker.
Chris Coyier found a great workaround last year but it’s not meant for every case.

A different solution

Turns out we can take advantage of the History API to do that quite easily. It’s just one line of code:

history.pushState(null, null, '#myhash');

and we can combine it with the old method of setting location.hash to cater for older browsers as well:

if(history.pushState) {
    history.pushState(null, null, '#myhash');
}
else {
    location.hash = '#myhash';
}

Browser support?

The History API is supported by:

  • Firefox 4+
  • Safari 5+
  • Chrome 8+
  • Coming soon in Opera

Enjoy :)

  • http://www.nahueljose.com.ar Nahuel

    Great! :) I’ve been looking for something like this before, good thing I can have it now 😛

  • Adam Taylor

    Nice. Unfortunately, what this doesn’t do is update the page’s CSS pseudo-selectors, so changes to :target selectors don’t seem to fire…

    I’ve been using :target to perform a JS-free hide and show of content, with a close button that links to an empty hash (‘#’). As you’d expect, this causes a page jump too, which I’d prefer not to have, and had hoped your solution would fix… Ah, well.

    (Did that make sense? :) )

    • http://leaverou.me Lea Verou

      Aw, that’s so sad :( I guess it won’t fire the hashchange event either :( 

      Thanks for the tip! 

    • DekuLink

      I know this post is 3 years old, but I figured I’d post the solution for anyone that comes across this. Surprisingly, the solution involves no javascript. The HTML for each tab looks like this:

      Tab content goes here

      The CSS:
      .tab {display:none;}
      .target {display:block;top:0;left:0;position:fixed;}
      .target:target + div {display:block !important;}

      The span will stay at the top of the viewbox, so when it scrolls to it, it won’t go anywhere. The + operator selects the div that imediately follows the :target-ed span with the “target” class.

      Hope I helped a few people! This solution was tough to find!

      • DekuLink

        Hmmm… Seems that the html didn’t work. It’s supposed to show a span right before the div. It looks like this: span class=”target” id=”tab1″

        I guess the messed it up. :/

    • Neil Monroe

      4 years later and this is still the case. To state it in slightly different terms, the main issue is that the element that originally had the :target pseudo-selector match on page load will still match even if the hash is updated. Because of this, you may have two tab contents visible at once.

      To make this work correctly, you will have to check the hash in script and update your applied styles that way.

  • http://pulse.yahoo.com/_RCDZENVSDKLPPWFRKJ6ML4NFJM Armindo

    If the history API is not supported:
    | var x = pageXOffset, y = pageYOffset;
    | location.hash = ‘#foo';
    | scrollTo(x,y);
    Have fun

    • http://leaverou.me Lea Verou

      What if the hash is changing through anchor links and not through code?

  • Anonymous

    Anchor Tag with class=’JumpToTop’ href=’#id-name’ and a java script$(‘.JumpToTop’).click(function() { var elementClicked = $(this).attr(“href”);  var destination = $(elementClicked).offset().top; $(“html:not(:animated),body:not(:animated)”).animate({ scrollTop: destination-20}, 1000 ); return false;});  
    will do a better work! what say?

  • http://twitter.com/theMOLITOR Chris Molitor

    Thanks for this! I had no idea about the History API. Really helps on AJAX projects.

  • http://b2bweb.fr/ Julien

    Hello,
    Just to say, it seem to work without the hash for location.hash = ‘myhash';

  • Pingback: How can I update window.location.hash without jumping the document? - Javascript Solution - Developers Q & A

  • Tobias Buschor

    a tricky alternative without flickering:

    var el = document.getElementById(‘myhash’);
    var id = el.id;
    el.removeAttribute(‘id’);
    location.hash = ‘myhash';
    el.setAttribute(‘id’,id);

    • http://www.surreality-rp.com/ ack!

      a hero to the people you are, sir.

    • firiz

      Thanks 😉 This is exactly what I want.

    • Andy

      One thousand internets to you sir

  • felix

    Thanks!!

  • http://demain-comme-jamais.toile-libre.org/ vb078

    hhaaaa I would like the same on my futur Website ::
    http://terminatorstudies.org/disnovation/#ICT
    You see, you scroll, the #is-automaticly-added
    :)

  • Adam

    Thanks for pointing me in the right direction with this!

    My use case was a little different in that I don’t want the back button to cycle though every tab click, but rather the actual previous page. Look at the W3C spec, (your link is broken btw: http://www.w3.org/TR/html5/browsers.html#the-history-interface) I made this instead, hopefully it helps someone looking for the same thing:

    // I.e. replace ‘pushState’ with ‘replaceState’
    if(history.replaceState) {
    history.replaceState(null, null, ‘#’ + hash);
    } else {
    location.hash = ‘#’ + hash;
    }

    Thanks,
    Adam