How can I update window.location.hash without jumping the document?

JavascriptJqueryHashScrollto

Javascript Problem Overview


I have a sliding panel set up on my website.

When it finished animating, I set the hash like so

function() {
   window.location.hash = id;
}

(this is a callback, and the id is assigned earlier).

This works good, to allow the user to bookmark the panel, and also for the non JavaScript version to work.

However, when I update the hash, the browser jumps to the location. I guess this is expected behaviour.

My question is: how can I prevent this? I.e. how can I change the window's hash, but not have the browser scroll to the element if the hash exists? Some sort of event.preventDefault() sort of thing?

I'm using jQuery 1.4 and the scrollTo plugin.

Many thanks!

Update

Here is the code that changes the panel.

$('#something a').click(function(event) {
	event.preventDefault();
	var link = $(this);
	var id = link[0].hash;
				
	$('#slider').scrollTo(id, 800, {
		onAfter: function() {
						
		    link.parents('li').siblings().removeClass('active');
			link.parent().addClass('active');
			window.location.hash = id;
						
			}
	});
});

Javascript Solutions


Solution 1 - Javascript

There is a workaround by using the history API on modern browsers with fallback on old ones:

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

Credit goes to Lea Verou

Solution 2 - Javascript

The problem is you are setting the window.location.hash to an element's ID attribute. It is the expected behavior for the browser to jump to that element, regardless of whether you "preventDefault()" or not.

One way to get around this is to prefix the hash with an arbitrary value like so:

window.location.hash = 'panel-' + id.replace('#', '');

Then, all you need to do is to check for the prefixed hash on page load. As an added bonus, you can even smooth scroll to it since you are now in control of the hash value...

$(function(){
    var h = window.location.hash.replace('panel-', '');
    if (h) {
        $('#slider').scrollTo(h, 800);
    }
});

If you need this to work at all times (and not just on the initial page load), you can use a function to monitor changes to the hash value and jump to the correct element on-the-fly:

var foundHash;
setInterval(function() {
    var h = window.location.hash.replace('panel-', '');
    if (h && h !== foundHash) {
        $('#slider').scrollTo(h, 800);
        foundHash = h;
    }
}, 100);

Solution 3 - Javascript

Cheap and nasty solution.. Use the ugly #! style.

To set it:

window.location.hash = '#!' + id;

To read it:

id = window.location.hash.replace(/^#!/, '');

Since it doesn't match and anchor or id in the page, it won't jump.

Solution 4 - Javascript

Why dont you get the current scroll position, put it in a variable then assign the hash and put the page scroll back to where it was:

var yScroll=document.body.scrollTop;
window.location.hash = id;
document.body.scrollTop=yScroll;

this should work

Solution 5 - Javascript

I used a combination of Attila Fulop (Lea Verou) solution for modern browsers and Gavin Brock solution for old browsers as follows:

if (history.pushState) {
	// IE10, Firefox, Chrome, etc.
	window.history.pushState(null, null, '#' + id);
} else {
	// IE9, IE8, etc
	window.location.hash = '#!' + id;
}

As observed by Gavin Brock, to capture the id back you will have to treat the string (which in this case can have or not the "!") as follows:

id = window.location.hash.replace(/^#!?/, '');

Before that, I tried a solution similar to the one proposed by user706270, but it did not work well with Internet Explorer: as its Javascript engine is not very fast, you can notice the scroll increase and decrease, which produces a nasty visual effect.

Solution 6 - Javascript

This solution worked for me.

The problem with setting location.hash is that the page will jump to that id if it's found on the page.

The problem with window.history.pushState is that it adds an entry to the history for each tab the user clicks. Then when the user clicks the back button, they go to the previous tab. (this may or may not be what you want. it was not what I wanted).

For me, replaceState was the better option in that it only replaces the current history, so when the user clicks the back button, they go to the previous page.

$('#tab-selector').tabs({
  activate: function(e, ui) {
    window.history.replaceState(null, null, ui.newPanel.selector);
  }
});

Check out the History API docs on MDN.

Solution 7 - Javascript

This solution worked for me

// store the currently selected tab in the hash value
    if(history.pushState) {
        window.history.pushState(null, null, '#' + id);
    }
    else {
        window.location.hash = id;
    }

// on load of the page: switch to the currently selected tab
var hash = window.location.hash;
$('#myTab a[href="' + hash + '"]').tab('show');

And my full js code is

$('#myTab a').click(function(e) {
  e.preventDefault();
  $(this).tab('show');
});

// store the currently selected tab in the hash value
$("ul.nav-tabs > li > a").on("shown.bs.tab", function(e) {
  var id = $(e.target).attr("href").substr(1);
    if(history.pushState) {
        window.history.pushState(null, null, '#' + id);
    }
    else {
        window.location.hash = id;
    }
   // window.location.hash = '#!' + id;
});

// on load of the page: switch to the currently selected tab
var hash = window.location.hash;
// console.log(hash);
$('#myTab a[href="' + hash + '"]').tab('show');

Solution 8 - Javascript

I'm not sure if you can alter the original element but how about switch from using the id attr to something else like data-id? Then just read the value of data-id for your hash value and it won't jump.

Solution 9 - Javascript

When using laravel framework, I had some issues with using a route->back() function since it erased my hash. In order to keep my hash, I created a simple function:

$(function() {   
    if (localStorage.getItem("hash")    ){
     location.hash = localStorage.getItem("hash");
    }
}); 

and I set it in my other JS function like this:

localStorage.setItem("hash", myvalue);

You can name your local storage values any way you like; mine named hash.

Therefore, if the hash is set on PAGE1 and then you navigate to PAGE2; the hash will be recreated on PAGE1 when you click Back on PAGE2.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionalexView Question on Stackoverflow
Solution 1 - JavascriptAttila FulopView Answer on Stackoverflow
Solution 2 - JavascriptDerek HunzikerView Answer on Stackoverflow
Solution 3 - JavascriptGavin BrockView Answer on Stackoverflow
Solution 4 - Javascriptuser706270View Answer on Stackoverflow
Solution 5 - JavascriptaldemarcalazansView Answer on Stackoverflow
Solution 6 - JavascriptMike VallanoView Answer on Stackoverflow
Solution 7 - JavascriptRohit DhimanView Answer on Stackoverflow
Solution 8 - JavascriptJon BView Answer on Stackoverflow
Solution 9 - JavascriptAndrewView Answer on Stackoverflow