I added a side navigation on this blog that selects the current header as the user scrolls down the page. One of my requirements was that it must change the URL to reflect the current header.
For instance, if you navigate to the header My Header, then the url would be /post/article-title#my-header.
Using document.hash
One way to do this is to change the hash using document.hash:
window.location.hash = "my-header";
This will set the hash value of the URL.
Using history state
The problem I found with the previous method was that it would try to load the url when the hash is set. This causes the page to lock or ping back when you scroll past each header.
The best solution would be one where the URL can be set, without the browser navigating to the URL.
Fortunately, we can do this by changing the browser history state directly:
history.pushState(state, title [, url])
There are three parameters for pushState:
- state is the state object representing the new history entry that we created when using pushState. Whenever we navigate to the new state, a popstate event is fired, and that event will contain a state property, which in itself contains a copy of the history entry's state object.
- title is the equivalent of document.title, and it is supposed to change the title of the page, however most browsers ignore this.
- url is an optional parameter which will change the current URL of the page without reloading it. It will reload it if the user refreshes the browser.
Since we aren't concerned with the state of the browser history, and the title gets ignored anyway, we only need to change the url:
history.pushState({}, "", "#my-header")
And now the URL changes without reloading, or navigating on the page. Perfect!