HTML5 Now: Getting More Through Polyfills
Dave Ward | May 17th, 2012
Taking advantage of HTML5 in real-world sites and applications can be a daunting proposition. Though modern browsers are implementing HTML5's new features at a rapid pace, few of us are lucky enough to write applications supporting only the latest crop of browsers. As a professional web developer, that browser fragmentation forces you to spend significant effort navigating the uncomfortable space between the promise of the future and the realities of the present. The good news is that Internet Explorer 10 and 9 support HTML5. Users are also leaving older versions of Internet Explorer. But the share of older versions is likely to remain just enough for developers to support in the foreseeable future.
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:
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 in Figure 1 tames Internet Explorer and coerces it to apply CSS styles to the <article> element.
Figure 1 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 in Figure 1 could be refactored to use html5shim as shown in Figure 2.
Figure 2 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 html5shim’s 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:
Pulling that data out at a later date becomes extremely easy:
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.
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.
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.
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.
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.
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.
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.
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:
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:
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, see this fantastic resource with brief descriptions and links to many of them here: https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-browser-Polyfills
About the Author
Find Dave on: