Organizational Research By

Surprising Reserch Topic

modifying document location hash without page scrolling


modifying document location hash without page scrolling  using -'javascript,jquery,fragment-identifier'

We've got a few pages using ajax to load in content and there's a few occasions where we need to deep link into a page. Instead of having a link to "Users" and telling people to click "settings" it's helpful to be able to link people to user.aspx#settings

To allow people to provide us with correct links to sections (for tech support, etc.) I've got it set up to automatically modify the hash in the URL whenever a button is clicked.  The only issue of course is that when this happens, it also scrolls the page to this element.

Is there a way to disable this?  Below is how I'm doing this so far.

$(function(){
    //This emulates a click on the correct button on page load
    if(document.location.hash){
     $("#buttons li a").removeClass('selected');
     s=$(document.location.hash).addClass('selected').attr("href").replace("javascript:","");
     eval(s);
    }

    //Click a button to change the hash
    $("#buttons li a").click(function(){
            $("#buttons li a").removeClass('selected');
            $(this).addClass('selected');
            document.location.hash=$(this).attr("id")
            //return false;
    });
});


I had hoped the return false; would stop the page from scrolling - but it just makes the link not work at all. So that's just commented out for now so I can navigate.

Any ideas?
    

asked Sep 16, 2015 by rajeevr642
0 votes
11 views



Related Hot Questions

13 Answers

0 votes

Step 1: You need to defuse the node ID, until the hash has been set. This is done by removing the ID off the node while the hash is being set, and then adding it back on.

hash = hash.replace( /^#/, '' );
var node = $( '#' + hash );
if ( node.length ) {
  node.attr( 'id', '' );
}
document.location.hash = hash;
if ( node.length ) {
  node.attr( 'id', hash );
}

Step 2: Some browsers will trigger the scroll based on where the ID'd node was last seen so you need to help them a little. You need to add an extra div to the top of the viewport, set its ID to the hash, and then roll everything back:

hash = hash.replace( /^#/, '' );
var fx, node = $( '#' + hash );
if ( node.length ) {
  node.attr( 'id', '' );
  fx = $( '
' ) .css({ position:'absolute', visibility:'hidden', top: $(document).scrollTop() + 'px' }) .attr( 'id', hash ) .appendTo( document.body ); } document.location.hash = hash; if ( node.length ) { fx.remove(); node.attr( 'id', hash ); }

Step 3: Wrap it in a plugin and use that instead of writing to location.hash...

answered Sep 16, 2015 by kinnari
0 votes

I think I may have found a fairly simple solution. The problem is that the hash in the URL is also an element on the page that you get scrolled to. if I just prepend some text to the hash, now it no longer references an existing element!

$(function(){
    //This emulates a click on the correct button on page load
    if(document.location.hash){
     $("#buttons li a").removeClass('selected');
     s=$(document.location.hash.replace("btn_","")).addClass('selected').attr("href").replace("javascript:","");
     eval(s);
    }

    //Click a button to change the hash
    $("#buttons li a").click(function(){
            $("#buttons li a").removeClass('selected');
            $(this).addClass('selected');
            document.location.hash="btn_"+$(this).attr("id")
            //return false;
    });
});

Now the URL appears as page.aspx#btn_elementID which is not a real ID on the page. I just remove "btn_" and get the actual element ID

answered Sep 16, 2015 by loknath.ganji
0 votes

Use history.replaceState or history.pushState* to change the hash. This will not trigger the jump to the associated element.

Example

$(document).on('click', 'a[href^=#]', function(event) {
  event.preventDefault();
  history.pushState({}, '', this.href);
});

Demo on JSFiddle

* If you want history forward and backward support

History behaviour

If you are using history.pushState and you don't want page scrolling when the user uses the history buttons of the browser (forward/backward) check out the experimental scrollRestoration setting (Chrome 46+ only).

history.scrollRestoration = 'manual';

Browser Support

answered Sep 16, 2015 by patelnikul321
0 votes

A snippet of your original code:

$("#buttons li a").click(function(){
    $("#buttons li a").removeClass('selected');
    $(this).addClass('selected');
    document.location.hash=$(this).attr("id")
});

Change this to:

$("#buttons li a").click(function(e){
    // need to pass in "e", which is the actual click event
    e.preventDefault();
    // the preventDefault() function ... prevents the default action.
    $("#buttons li a").removeClass('selected');
    $(this).addClass('selected');
    document.location.hash=$(this).attr("id")
});
answered Sep 16, 2015 by sumit_jaiswalmca
0 votes

I was recently building a carousel which relies on window.location.hash to maintain state and made the discovery that Chrome and webkit browsers will force scrolling (even to a non visible target) with an awkward jerk when the window.onhashchange event is fired.

Even attempting to register a handler which stops propogation:

$(window).on("hashchange", function(e) { 
  e.stopPropogation(); 
  e.preventDefault(); 
});

Did nothing to stop the default browser behavior. The solution I found was using window.history.pushState to change the hash without triggering the undesirable side-effects.

 $("#buttons li a").click(function(){
    var $self, id, oldUrl;

    $self = $(this);
    id = $self.attr('id');

    $self.siblings().removeClass('selected'); // Don't re-query the DOM!
    $self.addClass('selected');

    if (window.history.pushState) {
      oldUrl = window.location.toString(); 
      // Update the address bar 
      window.history.pushState({}, '', '#' + id);
      // Trigger a custom event which mimics hashchange
      $(window).trigger('my.hashchange', [window.location.toString(), oldUrl]);
    } else {
      // Fallback for the poors browsers which do not have pushState
      window.location.hash = id;
    }

    // prevents the default action of clicking on a link.
    return false;
});

You can then listen for both the normal hashchange event and my.hashchange:

$(window).on('hashchange my.hashchange', function(e, newUrl, oldUrl){
  // @todo - do something awesome!
});
answered Sep 16, 2015 by sandeep bhadauria
0 votes

if you use hashchange event with hash parser, you can prevent default action on links and change location.hash adding one character to have difference with id property of an element

$('a[href^=#]').on('click', function(e){
    e.preventDefault();
    location.hash = $(this).attr('href')+'/';
});

$(window).on('hashchange', function(){
    var a = /^#?chapter(\d+)-section(\d+)\/?$/i.exec(location.hash);
});
answered Sep 16, 2015 by dhananjayksharma
0 votes

I don't think this is possible. As far as I know, the only time a browser doesn't scroll to a changed document.location.hash is if the hash doesn't exist within the page.

This article isn't directly related to your question, but it discusses typical browser behavior of changing document.location.hash

answered Sep 16, 2015 by akasati02
0 votes

Okay, this is a rather old topic but I thought I'd chip in as the 'correct' answer doesn't work well with CSS.

This solution basically prevents the click event from moving the page so we can get the scroll position first. Then we manually add the hash and the browser automatically triggers a hashchange event. We capture the hashchange event and scroll back to the correct position. A callback separates and prevents your code causing a delay by keeping your hash hacking in one place.

var hashThis = function( $elem, callback ){
    var scrollLocation;
    $( $elem ).on( "click", function( event ){
        event.preventDefault();
        scrollLocation = $( window ).scrollTop();
        window.location.hash = $( event.target ).attr('href').substr(1);
    });
    $( window ).on( "hashchange", function( event ){
        $( window ).scrollTop( scrollLocation );
        if( typeof callback === "function" ){
            callback();
        }
    });
}
hashThis( $( ".myAnchor" ), function(){
    // do something useful!
});
answered Sep 16, 2015 by sujata naik
0 votes

Adding this here because the more relevant questions have all been marked as duplicates pointing here…

My situation is simpler:

  • user clicks the link (a[href='#something'])
  • click handler does: e.preventDefault()
  • smoothscroll function: $("html,body").stop(true,true).animate({ "scrollTop": linkoffset.top }, scrollspeed, "swing" );
  • then window.location = link;

This way, the scroll occurs, and there's no jump when the location is updated.

answered Sep 16, 2015 by deven.bendale
0 votes

Erm I have a somewhat crude but definitely working method.
Just store the current scroll position in a temp variable and then reset it after changing the hash. :)

So for the original example:

$("#buttons li a").click(function(){
        $("#buttons li a").removeClass('selected');
        $(this).addClass('selected');

        var scrollPos = $(document).scrollTop();
        document.location.hash=$(this).attr("id")
        $(document).scrollTop(scrollPos);
});
answered Sep 16, 2015 by amit.gupta
0 votes

The other way to do this is to add a div that's hidden at the top of the viewport. This div is then assigned the id of the hash before the hash is added to the url....so then you don't get a scroll.

answered Sep 16, 2015 by deven.bendale
0 votes

Here's my solution for history-enabled tabs:

    var tabContainer = $(".tabs"),
        tabsContent = tabContainer.find(".tabsection").hide(),
        tabNav = $(".tab-nav"), tabs = tabNav.find("a").on("click", function (e) {
                e.preventDefault();
                var href = this.href.split("#")[1]; //mydiv
                var target = "#" + href; //#myDiv
                tabs.each(function() {
                    $(this)[0].className = ""; //reset class names
                });
                tabsContent.hide();
                $(this).addClass("active");
                var $target = $(target).show();
                if ($target.length === 0) {
                    console.log("Could not find associated tab content for " + target);
                } 
                $target.removeAttr("id");
                // TODO: You could add smooth scroll to element
                document.location.hash = target;
                $target.attr("id", href);
                return false;
            });

And to show the last-selected tab:

var currentHashURL = document.location.hash;
        if (currentHashURL != "") { //a tab was set in hash earlier
            // show selected
            $(currentHashURL).show();
        }
        else { //default to show first tab
            tabsContent.first().show();
        }
        // Now set the tab to active
        tabs.filter("[href*='" + currentHashURL + "']").addClass("active");

Note the *= on the filter call. This is a jQuery-specific thing, and without it, your history-enabled tabs will fail.

answered Sep 16, 2015 by rajesh
0 votes

This solution creates a div at the actual scrollTop and removes it after changing hash:

$('#menu a').on('click',function(){
    //your anchor event here
    var href = $(this).attr('href');
    window.location.hash = href;
    if(window.location.hash == href)return false;           
    var $jumpTo = $('body').find(href);
    $('body').append(
        $('
') .attr('id',$jumpTo.attr('id')) .addClass('fakeDivForHash') .data('realElementForHash',$jumpTo.removeAttr('id')) .css({'position':'absolute','top':$(window).scrollTop()}) ); window.location.hash = href; }); $(window).on('hashchange', function(){ var $fakeDiv = $('.fakeDivForHash'); if(!$fakeDiv.length)return true; $fakeDiv.data('realElementForHash').attr('id',$fakeDiv.attr('id')); $fakeDiv.remove(); });

optional, triggering anchor event at page load:

$('#menu a[href='+window.location.hash+']').click();
answered Sep 16, 2015 by vijayshukla80

...