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.