Nowadays, lots of web applications are springing up. It’s common to make the browser back and forward button work as expected to improve the user experience. This post discusses some hash related topics and introduces serveral practical methods to manipulate the browser history.

The URL hash and HashChangeEvent

The basics

The URL’s hash part is what after the hash (i.e, #) mark of a URL. For example the hash part of http://www.example.com/page#hash1 is hash1, and some likes to refer it as fragment identifier. Some points noteworthy about the hash part are listed as follow:

  1. # works only on client side, and is not included in the HTTP request.
  2. Typically # represents a location in a page, and it can be saved into the browser bookmarks.
  3. Change # won’t trigger the browser to reload the page, it will raise a HashChangeEvent to the browser instead.
  4. Every time you change the # part, it will add a record into the browser history.

You can visit Fragment Identifier for detailed information about hash.

Your can use location.hash='anotherHash' to manually trigger the hashchange event. The dispatched HashChangeEvent has two fields: newURL and oldURL. newURL points to the new URL the window is navigating to and oldURL is the previous one.

A Vivid Example

Navigate to http://www.example.com/, open the browser console and paste the following code to listen to the HashChangeEvent:

1
2
3
4
window.onhashchange = function(event) {
console.log('old:', event.oldURL);
console.log('new:', event.newURL);
}

It will output the old and new URL when the HashChangeEvent is fired. Manually trigger it by

1
location.hash = 'section1'

It will log:

1
2
old: http://www.example.com/
new: http://www.example.com/#hash1

Now, raise HashChangeEvent again:

1
location.hash = 'section2'

It will log:

1
2
old: http://www.example.com/#hash1
new: http://www.example.com/#hash2

Click the back button of your browser, it will also raise a HashChangeEvent, and the output is:

1
2
old: http://www.example.com/#hash2
new: http://www.example.com/#hash1

Then click the forward button, the output will be:

1
2
old: http://www.example.com/#hash1
new: http://www.example.com/#hash2

That’s all hash and HashChangeEvent about. For some older browsers that don’t support the HashChangeEvent, you can use setInteval() to watch the URL change manually. Visit onhashchange to learn more.

Use Cases of HashChangeEvent

A typical use case for capturing HashChangeEvent is sending AJAX request to the server, and for SEO concern, you can use #! to enable the crawlers to visit the right pages. For example, if Google visits http://twitter.com/#!/username, it will crawler http://twitter.com/?_escaped_fragment_=/username so that it can get contents dynamically.

Another usage of HashChangeEvent is to customize the back and forward behavior of the browser. For example, if a user clicks a button to hide a chat box, you can mark it by setting the hash as chatBoxClosed, and if the user clicks the back button, you can capture the HashChangeEvent to show the chat box, and if the user then clicks the forward button, you can capture the HashChangeEvent and find the new hash is chatBoxClosed, then hide the chat box.

More complicated work can be done with lots of third party libraries. uriAnchor is one of them. It’s a jQuery plugin, and you can set a hash as follow:

1
2
3
4
5
6
7
$.uriAnchor.setAnchor({
page : 'profile',
_page : {
uname : 'wendy',
online : 'today'
}
});

The hash part will be #!page=profile:uname,wendy|online,today. You can also call $.uriAnchor.makeAnchorMap() to decode the hash part and return a map as:

1
2
3
4
5
6
7
{
page : 'profile',
_page : {
uname : 'wendy',
online : 'today'
}
}

For more advanced usage, please read the doc about uriAnchor.

The history Object

We can also manipulate the session history through the history object. Different from browser global history, a session history records the pages visited in the tab or frame that the current page is loaded in.

We can navigate between history pages through history.back() and history.forward(). We can also move to a certain page relative to the current one, e.g. history.go(-1); will move back one page and history.go(2) will move forward two pages. The history.length property tells the number of pages in the history stack.

Adding and Changing History Entries

The history.pushState() and history.replaceState() make it possible to manipulate history entries and associate a state object with each entry. Suppose you are at http://www.example.com/ now. You can add a history entry with a state by:

1
history.pushState({statePropery: 'statePropery1'}, "page1", "#section1");

The first parameter is a state object, in which you can store any related information; the second parameter is title, you can use it to name the state; the third one is the new URL. After the above command, the URL will be http://www.example.com/#section1, and the history.state object is {statePropery: 'statePropery1'}. You can also replace current history entry by:

1
history.pushState({statePropery: 'statePropery2'}, "page2", "#section2");

And the URL will be http://www.example.com/#section2, and the history.state object is {statePropery: 'statePropery2'}. You can even add a history entry without modifying the URL:

1
history.pushState({statePropery: 'statePropery3'}, "page2", "");

The current state will be {statePropery: 'statePropery3'}.

Some Notes:

  1. The URL parameter in pushState and replaceState must be under the same origin with the current page.
  2. pushState and replaceState will not load the page, they just change the URL, and modify the history stack.
  3. pushState and replaceState will not trigger HashChangeEvent or PopStateEvent (See below), event though only the hash part changes.

The PopStateEvent

Each time the user clicks the browser back or forward button, it will raise a PopStateEvent. If the destination history entry is added or changed with pushState and replaceState, you can obtain the copy of related state object with its state property. We can also manually raise this event by history.back(), history.forward() and history.go().

Browser Support Concern

The pushState, replaceState and window.onpopstate methods are relatively newer, and only IE 10 supports them. But the good news is that you can achieve it with history.js.

A Simple Comparison

  1. The HashChangeEvent only be raised if the hash part of URL changes, and you need to change the hash part to add a history entry. With the History API, i.e, pushState, replaceState and window.onpopstate, you can edit the URL more flexibly. You can change the URL into any under same origin and even add a history entry without modifying the URL.
  2. You need to encode and decode the data manually into and from the hash part with HashChangeEvent, while you can obtain the copy of related state object with the state property of PopStateEvent. Although you can move the encoding and decoding work to 3rd party libraries like uriAnchor, the History API is still more intuitive.
  3. For the sake of the browser support, nearly all modern browers (IE 8+) support onhashchange, while only IE 10+ support the History API and you need use history.js to bridge the gap.

Conclusion

This post discusses some hash related topics and gives several ways to manipulate the browser history. Some comparisons are made to help you choose the right technique to achieve your purpose. Nowadays, lots of complex web applications spring up with HTML5 and the browser support, it’s very important make sure that the back and forward button behavior properly. Hope it will help if you want to improve the UX of your web app by managing the browser history.