HTML5 Now: Getting More Through Polyfills

One of the most stubbornly persistent misconceptions about HTML5 is that it’s effectively unusable for years to come. With that myth and the uncertainty over fragmented browser support for HTML5’s new features, it’s easy to find someone with reasons for avoiding HTML5 right now.

But while some features are legitimately not ready for use in production, many of the new APIs in HTML5 have solidified enough to be relied on in newer browsers like Chrome, Firefox 4 and Internet Explorer 9. In fact, even Internet Explorer 6 includes support for one of HTML5’s “new” features, contentEditable. More importantly, many of the new features in HTML5 are compelling enough that waiting another decade to begin using them doesn’t make sense.

Cross-Browser Support

The most nagging issue with making the leap to HTML5 is that most of us have no choice but to support a variety of older browsers that have little or no support for the most useful new APIs. The thought of adopting a new Web technology conjures up nightmares of cross-browser inconsistencies, unmaintainable branching code, browser sniffing and a host of other problems. However, there’s an underappreciated technique that can entirely mitigate those problems for certain new features of HTML5 and still allow you to develop against the new APIs as though all your users had upgraded their browsers overnight: polyfills.

Polyfilling is a term coined by Remy Sharp to describe an approach for backfilling missing functionality in a way that duplicates the missing API. Using this technique allows you to write application-specific code without worrying about whether or not each user’s browser implements it natively. In fact, polyfills aren’t a new technique or tied to HTML5. We’ve been using polyfills such as json2.js, ie7-js, and the various fallbacks for providing transparent PNG support in Internet Explorer for years. The difference is the proliferation of HTML5 polyfills in the last year.

What Makes a Polyfill?

For a concrete example of what I’m talking about, take a look at json2.js. Specifically, here is the first line of code in its JSON.parse implementation:

if (typeof JSON.parse !== 'function') {
    // Crockford’s JavaScript implementation of JSON.parse 
}

 
By guarding the code with the typeof test, if the browser has a native implementation of JSON.parse, json2.js doesn’t try to interfere with or redefine it. If the native API is not available, json2.js implements a JavaScript version of JSON.parse in a way that’s exactly compatible with JavaScript’s native JSON API. Ultimately, this means you can include json2.js on a page and be confident of using JSON.parse without any regard for which browser your code is running in.

This shows the advantage of the polyfilling approach—to not only provide a compatibility layer, but to provide it in a way that attempts to closely mirror the standard API that the polyfill implements. Thus, none of the site-specific code needs to know or care about the compatibility layer’s existence. Ultimately, this results in cleaner, simpler, application-specific code that lets you take advantage of new APIs while still maintaining compatibility with older browsers.

HTML5’s New Semantic Elements

One of the new features in HTML5 that’s easiest to polyfill is the set of semantic elements that have been added, such as <article>, <aside>, <header > and <time>. Most of these elements render exactly as the venerable <div> and <span> do, but they impart richer, more specific semantic meaning.

Because these elements are valid SGML, the good news is that even older browsers like Internet Explorer 6 will display them today. However, one of Internet Explorer’s quirks is that it applies CSS styling only to elements that it recognizes. So, even though older versions of Internet Explorer do render the content of HTML5’s new semantic elements, they ignore any user-defined styling when doing so.

Luckily, Sjoerd Visscher discovered an easy workaround for Internet Explorer, and his approach was made popular by John Resig. Making a call to document.createElement() with any arbitrary element type specified as the argument causes Internet Explorer to recognize elements of that type and properly apply CSS styles to them as expected.

For example, adding a single call to document.createElement(‘article’) in the <head> of the document shown below tames Internet Explorer and coerces it to apply CSS styles to the <article> element.

<html>
    <head> 
        <title>HTML5 Now?</title> 
        <style> 
           article { margin: 0 auto; width: 960px; } 
        </style> 
        <script> 
            document.createElement(‘article’); 
        </script> 
    </head> 
    <body> 
        <article> 
            <!-- TODO: Write article… --> 
        </article> 
    </body> 
</html>

 
This call to document.createElement changes how Internet Explorer applies CSS styles.

Of course, no one wants to manually add createElement statements for each of the plethora of new semantic elements added in HTML5. Abstracting away that tedium is exactly where a polyfill shines. In this case, there’s a polyfill called html5shim (also known as html5shiv) that automates the process of initializing Internet Explorer compatibility with the new semantic elements.

For example, the code above could be refactored to use html5shim as shown below.

<html> 
    <head> 
        <title>HTML5 Now!</title> 
        <style> 
            article { margin: 0 auto; width: 960px; } 
        </style> 
        <!--[if lt IE 9]> 
        <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> 
        <![endif]--> 
    </head> 
    <body> 
        <article> 
            <!-- TODO: Write article… --> 
        </article> 
    </body> 
</html>

 
Using the html5shim polyfill

Notice the conditional comment surrounding the script reference to html5shim. This ensures that the polyfill will be loaded and executed only in versions of Internet Explorer earlier than version 9. No time is wasted downloading, parsing, and executing this code in browsers that already provide proper support for the new elements.

Another Alternative to Consider

If you’re interested enough in HTML5 to be reading this article, you’re probably aware of or using Modernizr already. However, one thing you might not be aware of is that Modernizr has the html5shim createElement functionality built-in. If you’re using Modernizr for feature detection, you already have backward compatibility for HTML5’s semantic elements.

Persistent Client-Side Storage

For years, we’ve had no choice but to hack together combinations of vendor-specific DOM extensions and proprietary plug-ins to solve the problem of persisting long-term state in the browser. These solutions included Firefox’s globalStorage, Internet Explorer’s userData, cookies and plug-ins like Flash or Google Gears. Though viable, these hacked-together workarounds are tedious, difficult to maintain, and prone to error.

To remedy this, one of the most warmly welcomed additions in HTML5 is a standards-based API for persistently storing data in the browser: localStorage. This storage API provides a consistent client-server key/value store, which can store up to 5 MB of isolated data for each Web site a user visits. You can think of localStorage as a massive cookie that is easier to work with and isn’t needlessly transmitted back and forth between the browser and server during every HTTP request. The localStorage feature is perfect for tasks that require browser-specific data, like remembering preferences and locally caching remote data.

The localStorage feature is already supported in every A-grade browser, including Internet Explorer 8, but it’s missing from older versions of most browsers. In the meantime, several solutions have arisen to polyfill cross-browser storage into those older browsers. They range from the simplicity of Remy Sharp’s Storage polyfiller, to the comprehensive backward compatibility provided by store.js and PersistJS, to the full-featured API of LawnChair and the AmplifyJS storage module.

For example, this is how you might use the AmplifyJS storage module to persist some data in a user’s browser without resorting to cookies—even if that user was using Internet Explorer 6:

// Sets a localStorage variable 'Name' with my name in it.
amplify.store('name', 'Dave Ward');
 
var website = {
        name: 'Encosia',
        url: 'http://encosia.com'
}
 
// The library takes care of serializing objects automatically.
amplify.store('website', website);

 
Pulling that data out at a later date becomes extremely easy:

// The values we stored before could then be used at a later time, even
// during a different session.
var $personLink = $('<a>', {
        text: amplify.store('name'),
        href: amplify.store('website').url
});
 
$personLink.appendTo('body');

 
Again, the great thing about using localStorage or a localStorage-based API is that none of this data needs to be persisted in cookies and then be transmitted along with every HTTP request, nor does it require that you invoke a heavyweight plug-in like Flash just to store a bit of data. The data is stored in a true, isolated local storage mechanism, which makes it great for caching data locally or developing sites that have rich support for offline usage.

What to Use?

Remy Sharp’s Storage polyfiller is the only one that truly qualifies as a polyfill, because the others don’t exactly mimic the HTML5 localStorage API. However, store.js and the AmplifyJS storage module provide support for a wider range of fallback approaches to achieve compatibility in older browsers. Pragmatically, that’s hard to ignore.

Geolocation

Geolocation is another HTML5 feature ripe for polyfilling. If both browser and operating system support geolocation and are running on a device with a GPS sensor, HTML5 provides access to a geolocation API that allows JavaScript code to determine where your page is being accessed from.

Mobile devices are the most impressive example of browser-based geolocation. By coupling their built-in GPS hardware with modern browsers that support the HTML5 geolocation API, both Android and iOS devices support native HTML5 geolocation with as much accuracy as their native apps.

The JavaScript necessary to access geolocation data in those optimal environments is as simple as this:

navigator.geolocation.getCurrentPosition(function(position) {
    var lat = position.coords.latitude;
    var long = position.coords.longitude;
 
    console.log('Current location: ', lat, log);
});

That’s well and good for mobile apps, but desktop hardware doesn’t typically contain a GPS sensor. We’re all accustomed, however, to the location-aware advertising that’s been following us around the Internet on desktop hardware for far longer than the geolocation API has existed, so it’s obviously possible to work around the lack of a GPS on desktop browsing environments.

In JavaScript, the current workaround is to look up a visitor’s IP address in a database of known IP locations. That approach suffers from significantly lower accuracy than using a GPS device, but these databases are usually able to locate an IP address within the correct regional area, which is enough to be useful for many applications.

You may be aware of techniques for more accurate GPS-less geolocation that don’t rely solely on IP address lookups. Most often, those enhanced estimations are accomplished by the novel approach of comparing visible Wi-Fi hotspot identifiers with a database of where those particular combinations of hotspots have been physically located in the past.

Unfortunately, JavaScript code running in a browser is not currently privy to that data from the operating system. So, the Wi-Fi–based technique is not available to polyfills for the foreseeable future, leaving us with IP lookups as the only alternative.

Paul Irish wrote a simple geolocation polyfill that provides some level of geolocation in older browsers and on hardware lacking a GPS sensor. It accomplishes this by using Google’s geolocation API to translate a visitor’s IP address to an approximate physical location. It’s a true polyfill in the sense that it plugs its geolocation functionality into the navigator.geolocation object, but only if the browser doesn’t natively provide the geolocation API.

Browser History and Navigation

As superficial DHTML effects give way to more structural client-side features such as AJAX-based paging and single-page interfaces, those structural changes begin to fall out of sync with the browser’s built-in navigation and history functionality. Then, when users intuitively attempt to use their Back button to navigate to a previous page or application state, things go badly. Searching for “disable the back button” reveals the extent to which this problem plagues modern Web development.

Manipulating the “hash” portion of the browser’s location helps tackle one side of the problem. Since the hash was originally intended for jumping between navigation points within the same page, changing a URL’s hash doesn’t trigger a page refresh like changes to the underlying URL prefix do. Leveraging that property of the hash allows client-side updates to keep the browser’s displayed address in sync with JavaScript-driven changes that happen without traditional navigation events.

The onhashchange Event

While manipulating the browser’s hash is well supported, reaching back beyond even Internet Explorer 6, a standardized method for monitoring changes to the hash has been more elusive until recently. The current crop of browsers support an onhashchange event, which is triggered when the hash portion of the address changes—perfect for detecting when a user attempts to navigate through client-side state changes by using the browser’s navigation controls. Unfortunately, the onhashchange event is only implemented in relatively new browsers, with support beginning in Internet Explorer 8 and Firefox’s 3.6 release.

Though the onhashchange event isn’t available in older browsers, there are libraries that provide an abstraction layer in older browsers. These compatibility shims use browser-specific quirks to replicate the standard onhashchange event, even resorting to monitoring location.hash several times per second and reacting when it changes in browsers with no alternative methods. One solid choice in that vein is Ben Alman’s jQuery Hashchange plug-in, which he extracted from his popular jQuery BBQ plug-in. Alman’s jQuery Hashchange exposes a hashchange event with remarkably deep cross-browser compatibility. I hesitate to call it a polyfill because it requires jQuery and doesn’t exactly duplicate the native API, but it works great if you’re using jQuery on your pages already.

Beyond HashState

Manipulating the hash is a good start toward solving the client-side state-management problem, but it’s not without its drawbacks. Hijacking a legitimate browser navigation feature isn’t optimal since the hash-based URL structure can cause confusion for users and conflict with existing on-page navigation.

An even more fundamental problem is that browsers do not include the hash portion of requested URLs in the HTTP requests. Without access to that portion of the URL, it’s impossible to immediately return a page that’s in the same state as one that the user bookmarked, received via email, or discovered through social sharing. That leads to sites having no alternative but to display pages in their default, initial state and then automatically trigger a jarring transition to the state that the user actually desires. To find evidence of the impact this has on usability, you need look no further than the widespread negative reaction to Twitter and Gawker Media’s “hash bang” redesigns.

Enter pushState

Luckily, HTML5 has also introduced a pair of more advanced APIs that significantly improve the client-side history-management situation. Often referred to simply as pushState, the combination of the window.history.pushState method and the window.onpopstate event provides an avenue for asynchronously manipulating the entire path portion of the browser’s address and, likewise, reacting to navigation events outside the hash.

Browsing through the source for a project on GitHub is one of the best real-world examples of using pushState right now. Since manipulating the browser’s address with pushState doesn’t cause a full page refresh like traditional changes to the address, GitHub is able to provide animated transitions between each “page” of code while still retaining user-friendly URLs that aren’t crufted up with hashes or querystrings.

Better yet, if you save a bookmark to one of these URLs and navigate directly to it later, GitHub is able to immediately serve the correct content to you on the first request because the client-side URL structure matches what they use on the server. As I mentioned earlier, doing this is impossible when you use hash-based URLs because your Web server is never privy to the hash portion of requests.

Using onhashchange and pushState in Your Own Code

Unfortunately, to truly polyfill pushState functionality into browsers that don’t support it is impossible. No abstraction layer can change the fact that modifying the URL in older browsers will trigger a page load. However, you can have the best of both worlds by using pushState in browsers that implement it and then fall back to using the hash-based approach in older browsers.

Benjamin Lupton has assembled a great cross-browser library to smooth over the wide range of quirks and inconsistencies that come along with maintaining client-side history. His library covers browsers all the way from Internet Explorer 6 to the latest version of Chrome. Using the library is simple. It has a syntax that closely follows HTML5’s own pushState syntax:

// This changes the URL to /state1 in HTML5 browsers, and changes it to
// /#/state1 in older browsers.
History.pushState(null, 'State 1', 'state1');
 
// Same as before, but /state2 and /#/state2.
History.pushState(null, 'State 2', 'state2');

Rather than exposing an exact replica of the HTML5 popstate event, history.js includes a variety of adapters to work with the eventing systems in those libraries. For example, using the jQuery adapter, you could bind an event handler to the history.js statechange event like this:

History.Adapter.bind(window, 'statechange', function() {
    // Get the new history state from history.js.
    var state = History.getState();
 
    // Write the URL we’ve navigated to on the console.
    console.log(state.url);
});

This statechange event handler is triggered any time the browser navigates through history points that have been persisted via the history.js pushState method. Whether in an HTML5 browser that supports pushState natively or in an older browser that supports only hash-based URL changes, monitoring this single event catches any activity.

Putting this to work in real-world applications is easy. You can probably imagine using it in conjunction with AJAX-powered grid paging and sorting, or even for the navigation for an entire site (Gmail or Twitter, for example) without resorting to those universally loathed hash-bang URLs and redirects.

Running with pushScissors

One thing to watch out for when using pushState is that you must take care that your server will respond correctly to every URL that you use on the client-side. Since it’s easy to build up a client-side URL that your server will respond to with a 404 or 500 error (for example, /undefined), it’s a good idea to be sure that your server-side routing or URL rewriting is configured to handle unexpected URLs as gracefully as possible. For example, if you have a multipage report at /report, with pushState-driven URLs of /report/2, /report/3, and so on for each page, you should ensure that your server-side code responds gracefully to requests for URLs like /report/undefined.

A less-desirable alternative is to use querystring URL fragments in your pushState address updates, like /report?page=2 and /report?page=3. The resulting URLs don’t look as nice, but they are at least unlikely to result in a 404 error.

Where to Go from Here

This article only scratches the surface of the HTML5 polyfills ecosystem. There are active projects that provide cross-browser support for features such as SVG and canvas graphics, HTML5 video, ECMAScript 5, and even WebWorkers. If you’re interested in learning more about these projects, Paul Irish has assembled a fantastic resource with brief descriptions and links to many of them here: https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-browser-Polyfills.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Matt

    Its probably a good idea to flag that this technique depends on JavaScript being enabled in the browser. You didn’t explicitly state this although it could be implied.

    We get a lot of customers come to us saying they won’t HTML5 because there is so much buzz. But if they need to support older browsers such as those used in big corporates or financial institutions they also need javascript enabled which is not always the case.

    So whilst this is an approach to HTML5 you then need to either discard all non javascript enabled users or come up with an approach for serving them off a different site or otherwise mitigating the issue.

    I think the blogger and commentators need to discuss the pros and cons of a particular approach.

  • http://www.codeconquest.com/ Charles @ CodeConquest.com

    Thanks for this thorough article Dave. I have to say, IE ignoring style for elements it doesn’t recognize is strange.

  • http://www.outwardfocusdesign.com Christopher Cox

    Great article. I’m familiar with and have used a bunch of these, but the details you provide with the history/back button stuff are really helpful. I’ve been working on a one-page application that heavily uses ajax techniques, and a future iteration of it will have history states.

  • http://afarkas.github.com/webshim/demos/ Andrew

    There’s a wonderful polyfill named Webshims Lib, maintained by Alex Farkas at http://afarkas.github.com/webshim/demos/ which lazy-loads appropriate polyfills for many of the most valuable HTML5 features. Although it has two dependencies: Modernizr and jQuery, it is incredibly easy to use and provides a high level of functionality. You might find “better” polyfills for specific HTML5 features, but Webshims Lib is a great all-around solution for many use cases.

  • Christian

    I’ve been using ie-7.js for a long time now but is it correct that it has been somewhat replaced by html5shim which itself has been somewhat replaced by Modernizr? I had been under the impression that Modernizr simply checks to see if a certain feature exists in a particular UA and then the developer could do or not do different things depending on the presence or absence of a particular feature. But if does all that PLUS helps older browsers recognize newer elements then apparently I can remove the html5shim. But I think ie-7.js is the only polyfill to help older browsers recognize newer CSS. Is that correct? Seeking clarification…

  • http://www.infraredstudios.co.uk Salv

    @Matt – good point well made. I was totally on-board with the whole modernizr thing for a moment there and then you bought it back around, it all relies on javascript being turned on.

    I too would like to hear more discussion about other ways around this if javascript is off, i.e do you serve up an entirely different site although that then causes headaches in maintaining 2 sites. Be interested to hear other thoughts…

  • http://encosia.com Dave Ward

    When Modernizr initializes, one of the things it does is remove the .no-js CSS class from your root html element (if that class is present there). So, if you add that class to the html tag on all of your pages on the server-side, you can use “.no-js” CSS rules to alter your UI appropriately for no-js users. For example: .no-js .geolocation-API-dependent-button { display: none; }

    Beyond that, there’s not an awful lot you can do to better support no-js users in the context of using HTML5 APIs on a given page. Since you need JavaScript to control most of the new features in HTML5, a no-js user is effectively also not an HTML5 user.

  • http://nicksh.com Nika Shvelidze

    Better code for HTML5Shim (HTML5Shiv)
    //cdnjs.cloudflare.com/ajax/libs/html5shiv/3.6.1/html5shiv.js