- Key Takeaways
- Cross-Browser Support
- What Makes a Polyfill?
- HTML5’s New Semantic Elements
- Another Alternative to Consider
- Persistent Client-Side Storage
- What to Use?
- Geolocation
- Browser History and Navigation
- The onhashchange Event
- Beyond HashState
- Enter pushState
- Using onhashchange and pushState in Your Own Code
- Running with pushScissors
- Where to Go from Here
- Frequently Asked Questions (FAQs) about HTML5 and Polyfills
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.
Key Takeaways
- HTML5 is increasingly usable with the aid of polyfills, which allow developers to utilize modern web APIs in browsers that do not natively support them.
- Polyfills work by replicating the missing APIs in older browsers, enabling consistent functionality across different user environments.
- The use of HTML5 semantic elements like
, - LocalStorage and other HTML5 features offer significant improvements over older technologies like cookies, and can be polyfilled to work in older browsers using libraries such as AmplifyJS.
- Advanced HTML5 features like geolocation and pushState enhance user experience and site functionality, though their full benefits are sometimes limited to newer browsers.
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 itsJSON.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 html5shimcreateElement
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’sglobalStorage
, 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 HTML5localStorage
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 anonhashchange
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 aspushState
, 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 polyfillpushState
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 usingpushState
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.Frequently Asked Questions (FAQs) about HTML5 and Polyfills
What exactly is a polyfill in the context of HTML5?
A polyfill is a piece of code (usually JavaScript) that provides the technology that you, as a developer, expect the browser to provide natively. Polyfills allow you to write your code as if these advanced features were built in, and if a browser does not support them, the polyfill will provide an appropriate fallback.
Why are polyfills important in HTML5 development?
Polyfills are crucial in HTML5 development because they ensure that your web applications work across different browsers, even those that do not support certain HTML5 features. They provide a consistent and reliable user experience across all platforms.
How do I use a polyfill in my HTML5 project?
To use a polyfill, you first need to identify the HTML5 feature that you want to use but isn’t supported in all browsers. Then, you find a polyfill that provides this feature, and include it in your project. The polyfill will detect if the browser supports the feature, and if not, it will implement it.
Are there any drawbacks to using polyfills?
While polyfills are incredibly useful, they do come with some drawbacks. They can add extra weight to your web pages, which can slow down load times. Additionally, not all polyfills are created equal – some may not fully or accurately replicate the native functionality.
Can I create my own polyfills?
Yes, you can create your own polyfills. However, it requires a deep understanding of JavaScript and the feature you’re trying to replicate. It’s often easier and more efficient to use existing, well-tested polyfills.
What are some popular polyfills for HTML5 features?
There are many popular polyfills available for various HTML5 features. Some examples include Modernizr, which tests for HTML5 and CSS3 features and adds classes to the HTML element for you to key your CSS against, and html5shiv, which enables use of HTML5 sectioning elements in legacy Internet Explorer.
How do I know if a polyfill is needed?
You can use feature detection to determine if a polyfill is needed. This involves testing if a certain feature is supported in the user’s browser, and if not, loading a polyfill to provide that functionality.
Are polyfills the only way to ensure cross-browser compatibility?
No, polyfills are not the only way to ensure cross-browser compatibility. Other methods include graceful degradation (building your web application to the highest standard and then adding fallbacks for older browsers) and progressive enhancement (starting with a basic version of your web application and then adding features as the browser allows).
Do I always need to use polyfills when working with HTML5?
No, you don’t always need to use polyfills when working with HTML5. If you’re only targeting modern browsers that support all the HTML5 features you’re using, then you won’t need any polyfills. However, if you want to ensure that your web application works in as many browsers as possible, then polyfills can be very useful.
Where can I find reliable polyfills for HTML5 features?
There are many resources online where you can find reliable polyfills. Some popular ones include the Modernizr website, the HTML5 Cross Browser Polyfills wiki on GitHub, and the Polyfill service from the Financial Times.
Dave Ward is an independent ASP.NET consultant in Atlanta, Georgia, specializing in creating functional, interactive web applications with HTML, CSS, and JavaScript. With over 15 years’ experience developing websites, he has been recognized as a Microsoft ASP.NET/IIS MVP and a member of the ASP Insiders.