Develop a Windows 8 app in 30 days
Building fast and functional sites is a challenge with which most Web developers are familiar. Loading a new page every time the user clicks a link is slow. Fetching all content dynamically effectively disables the back button. Working with hashes is better, but still not ideal. Internet Explorer 10 in the Windows Developer Preview eliminates the compromise by adding support for HTML5 History. The pushState
, replaceState
, and popstate
APIs provide fine-grained control over the behavior of the back button and the URL presented to the user for dynamic content. Together these APIs help you improve the performance of your site without sacrificing usability.
If you’re not already familiar with the HTML5 History APIs, think of pushState
as being the dynamic equivalent of navigating to another page. Similarly, replaceState
is much like location.replace
. The difference is these APIs leave the current page intact when updating the session history by storing states instead of pages. Both pushState
and replaceState
take three parameters: a data object, a title, and an optional URL.
history.pushState(data, title, url);
history.replaceState(data, title, url);
Note that the title parameter to pushState
and replaceState
is ignored by most browsers, including IE10. If you want, you can still provide this information since future browsers may expose it as part of their history UI.
Setting Custom URLs
The URL parameter to pushState
and replaceState
can be used to update the URL of the page without navigating. To illustrate, consider you’ve loaded "http://www.contoso.com/index.html." Using hash changes, you can only append to the URL:
location.hash = "about.html";
But with pushState
and replaceState
you can point to a completely different page on your site without actually going there:
history.pushState(null, "About", "/about.html");
Make sure your server can handle all dynamic URLs you create so things like favorites still work. You can also add some data to the state object so you don’t have to parse the full URL later to restore the state:
history.pushState("about.html", "About", "/about.html");
The protocol, hostname, and port must match the current URL, but the path, query, and fragment are fair game for customization. This enables associating dynamic states with URLs that are easily backed by the server and work even when script is disabled. Ultimately, this lets you dynamically fetch and display only the data that changes from page to page while keeping the user experience intact.
Restoring Saved States
You should restore state after navigating the history (for example, when the user presses the back button) or reloading the page. Restoring state is accomplished by listening to the popstate event
. The popstate event
fires when state changes as a result of history navigation. At this point the data object for the destination state can be retrieved via history.state
. In cases where the page is reloaded the popstate event
will not fire, but history.state
can still be accessed at any time during or even after the load. Thus code like the following can restore state at the appropriate times:
function init() {
loadState();
window.addEventListener("popstate", loadState, false);
}
function loadState() {
var state = history.state;
}
init();
Storing Complex, Dynamic Data
The data object stored in a state can be more than a string. Custom JavaScript objects and even some native types such as ImageData
can also be used. The data provided is saved using the structured clone algorithm, which preserves complex relationships such as cycles and multiple references to the same object. This makes saving and restoring even complex objects a breeze as illustrated in this simple demo. In the demo, snapshots of a canvas are captured to create an undo stack using code like the following:
function init() {
loadState();
window.addEventListener("popstate", loadState, false);
}
function stopDrawing() {
var state = context.getImageData(0, 0, canvas.width, canvas.height);
history.pushState(state, "");
}
function loadState() {
var state = history.state;
if (state) {
context.putImageData(state, 0, 0);
}
}
To change this to keep track of the current state without tracking each change, you can use replaceState
instead of pushState
.
Size Considerations
HTML5 History makes pushing large amounts of data onto the stack easy if you’re not careful. For example, the undo demo above stores ~0.5MB per state and could easily use more if the canvas was larger. This data will consume memory as long the associated state entry remains in the session history, which can be long after the user leaves your site. The more data you store, the sooner a browser may start clearing your entries out to save space. In addition, some browsers also enforce a hard limit on the amount of data that can be stored with a single call to pushState
or replaceState
.
Cross-Browser Considerations
As always, use feature detection to handle differences in support across browsers. Since most of HTML5 History involves events and properties, the only new parts that truly require detection are calls to pushState
and replaceState
:
function stopDrawing() {
var state = context.getImageData(0, 0, canvas.width, canvas.height);
if (history.pushState)
history.pushState(state, "");
}
Such detection will at least keep your script from failing in older browsers. Depending on your scenario, you may want to start with full-page navigations and upgrade to dynamic content when HTML5 History is supported. Alternatively, you can use a history framework or polyfill to keep the back button working, but keep in mind that not everything can be emulated. For example, dynamic control over the path and query components of an URL can only be achieved via pushState
and replaceState
.
Note that some browsers support an earlier draft of HTML5 History with two notable differences from the current draft:
- The
popstate
event fires even during page load - The
history.state
property does not exist
In order to support these browsers, you can fall back to reading the state information off the popstate
event itself.
Wrap Up
Overall, the HTML5 History APIs provide a great deal of flexibility for building responsive and usable Web sites. With some care taken for legacy browsers, these APIs can be used today to great effect. Start testing them with your site in IE10 on the Windows Developer Preview and send feedback via Connect.