September 2011

Volume 26 Number 09

Building Apps with HTML5 - No Browser Left Behind: An HTML5 Adoption Strategy

By Brandon Satrom | September 2011

There’s a lot to be excited about with HTML5. With new markup, CSS capabilities and JavaScript APIs, the scope of what’s possible on the Web is growing by leaps and bounds. Add to that the steady one-upmanship among browser vendors, and the list of exciting features expands almost daily. From nightly builds to dev channel releases and regular platform previews, browsers are changing fast and Web developers everywhere are going along for the exhilarating ride.

But as much as the development and browser communities are pushing the HTML5 hype up to a fever pitch, the vast majority of people on the Web aren’t using the brand-new browsers and versions that we are. If you’re a Web developer in a large development shop or an enterprise with a large user base, you probably already know this. Even if you’re working with a small shop or startup providing some service via the Web, you probably spend a lot of time making sure your site caters to as many browsers and browser versions as possible.

Given this reality, it’s easy to see HTML5 not in terms of whether it’s ready to be used today, but whether you’re ready for it. For example, let’s suppose you’ve created a page with some of the new semantic tags (like <header> and <article>), added some new CSS features (like border-radius and box-shadow), and even added a <canvas> element to draw an HTML5 logo on your page.

In newer browsers like Internet Explorer 9, Firefox 4 and later, or Google Chrome, this will render as depicted in Figure 1. But if you attempt to load this page in Internet Explorer 8 or earlier, you’ll see something more like Figure 2: an utterly broken page.

Semantic Page with Styles and an HTML5 <canvas> Element, Rendered in Internet Explorer 9
Figure 1 A Semantic Page with Styles and an HTML5 <canvas> Element, Rendered in Internet Explorer 9

The Same Semantic Page, Rendered in Internet Explorer 8 with No Styles and No <canvas>
Figure 2 The Same Semantic Page, Rendered in Internet Explorer 8 with No Styles and No <canvas>

I wouldn’t blame you if you looked at all of the great features of HTML5 and, after having an experience like this, told yourself that the best course was to wait. It’s easy to come to the conclusion that, ready or not, HTML5 isn’t quite ready for you, or your users.

Before you make the decision to set a date in 2022 to take another look at HTML5, I ask that you read the rest of this article. My goal this month is to give you practical strategies for how you can adopt HTML5 technologies today without ending up with the kind of graceless degradation illustrated in Figure 2. In this article, I’ll cover:

  1. Feature detection versus user agent (UA) sniffing
  2. Polyfilling with JavaScript
  3. Graceful degradation

These three subjects should tell you much of what you need to know to build Web sites for a broad spectrum of browsers. By the time we’re finished, you’ll have a solid strategy for adopting HTML5 technologies with confidence and without delay. You’ll also have some tools you can use to progressively enhance sites for newer browsers, while gracefully degrading for others.

The Importance of Feature Detection

In order to provide stable and consistent experiences across browsers, developers often need to have some information about a user’s browser. Historically, it was common to determine that information with JavaScript like this:

var userAgent = navigator.userAgent;
 
if (userAgent.indexOf('MSIE') >= 0) {
  console.log("Hello, IE user");
} else if (userAgent.indexOf('Firefox') >= 0) {
  console.log("Hello, Firefox user");
} else if (userAgent.indexOf('Chrome') >= 0) {
  console.log("Hello, Chrome user");
}

This technique, known as UA sniffing, is widely used for determining which browser is requesting your page. The logic is that by knowing the user’s browser (Internet Explorer 7, for instance), you can make runtime decisions about what features of your site to enable or disable. UA sniffing is tantamount to saying to the browser: “Who are you?”

The problem with this approach is that browsers can be made to lie. The UA string is a user-configurable piece of information that doesn’t really provide a 100 percent accurate picture of the browser in question. What’s more, as this technique became widely embraced, many browser vendors added extra content to their own UA strings as a way to trick scripts into drawing incorrect assumptions about which browser was being used, thus routing around detection. Some browsers now even include a facility that allows users to change their UA string with just a few clicks.

The goal of UA sniffing was never to know the user’s browser and version, though. And it certainly wasn’t intended to give you an avenue to tell your users to “go download another browser” if they used one you didn’t like—even if that technique is used by some. Users do have a choice in the browser they use, but our responsibility as developers is to provide the most reliable and consistent experience, not to impose an opinion of browser preference upon them. The goal of UA sniffing was always to give you an accurate picture of the capabilities or features that you could leverage within the user’s current browser. Knowledge of the browser itself is just a means to that information.

Today there are alternatives to UA sniffing, and one that’s growing in popularity—thanks in part to both jQuery and Modernizr—is called object or feature detection. These terms are mostly interchangeable, but I’ll stick to “feature detection” for this article.

The goal of feature detection is to determine if a given feature or capability is supported on the user’s current browser. If UA sniffing is like asking the browser “who are you,” feature detection is like asking the browser “what are you capable of”—a much more direct question and a more reliable way for you to provide conditional functionality to users. It’s much tougher for users and browsers to fake or erroneously report feature support, assuming feature detection scripts are correctly implemented.

Manual Feature Detection

So what does feature detection look like, as opposed to the UA sniffing example? To answer that, let’s look at fixing the issues that arose when viewing my HTML5 page, depicted in Figure 1, in Internet Explorer 8 instead of Internet Explorer 9. The markup for this page is listed in Figure 3.

Figure 3 A Page with New Semantic HTML5 Markup

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>My Awesome Site</title>
  <style>
    body { font-size: 16px; font-family: arial,helvetica,clean,sans-serif;  }
    header h1 { font-size: 36px; margin-bottom: 25px; }
    article 
    {
      background: lightblue;
      margin-bottom: 10px;
      padding: 5px;
      border-radius: 10px;
      box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.5);
    }
    article h1 { font-size: 12px; }
  </style>
</head>
<body>
  <header><h1>My Awesome Site</h1></header>
  <article>
    <header><h1>An Article</h1></header>
    <p>Isn't this awesome?</p>
  </article>
  <canvas width="250" height="500"></canvas>
</body>
<script src="../js/html5CanvasLogo.js" type="text/javascript"></script>  
</html>

The differences between Internet Explorer 9 and Internet Explorer 8, as shown in Figures 1 and 2, are drastic. For starters, my page is completely unstyled, as though the CSS for this page doesn’t exist. What’s more, I’m missing the fancy HTML5 shield at the bottom of the page. Each of these problems can be fixed easily, and feature detection is the first step to identifying the problem.

The cause of both issues is simple: <header>, <article> and <canvas> are not valid HTML elements as far as Internet Explorer 8 is concerned, and, as such, I can’t work with them. To resolve the <canvas> issue, instead of using UA sniffing to determine what browser/version is being used, I’d like to ask the browser, via JavaScript, if the <canvas> element and its JavaScript APIs are supported. My feature check for canvas looks like this:

!!document.createElement('canvas').getContext

This statement does a couple of things. First, it uses double negation (!!) to force undefined values to be explicitly false. Then, it manually creates a new canvas element and attaches it to the DOM. Finally, it invokes getContext, a new function available to <canvas> elements as a way of manipulating the Canvas API via JavaScript. If I’m using Internet Explorer 9, this statement will return true. If I’m using Internet Explorer 8, getContext will return “undefined,” which my double negation will coerce to false.

That’s feature detection at its most basic. With this statement and others like it, I now have a more reliable way to query feature support within the browser. For more information on manual feature detection, visit diveintohtml5.info/everything.html.

Using Modernizr for Feature Detection

Manual feature detection is certainly an improvement on UA sniffing, but this approach still leaves you to do the heavy lifting for both detecting the availability of a feature and deciding what to do if that feature doesn’t exist. And while the canvas example was a simple one requiring one line of code, this isn’t the case for every feature you may want to detect—nor is the detection code the same across all browsers. Detecting support for the CSS3 modules I used earlier (border-radius and box-shadow), for instance, can be a bit trickier.

Thankfully, Modernizr (modernizr.com) offers a better approach. Modernizr is a JavaScript library that “… detects the availability of native implementations for next-generation Web technologies, i.e. features that stem from the HTML5 and CSS3 specifications.” Adding a reference to Modernizr in your pages supplies four major features:

  1. A comprehensive list of features supported that’s cleverly added to your markup, which enables conditional CSS definitions.
  2. A JavaScript object that aids in script-based feature detection.
  3. All of the new HTML5 tags added to the DOM at run time, for the benefit of Internet Explorer 8 and previous Internet Explorer browsers (more on that in a moment).
  4. A script loader for conditionally loading polyfills into your pages.

We won’t discuss the first item any further in this article, though I encourage you to head over to modernizr.com and check out the documentation on this and the rest of the features.

The second item is the feature that helps turn this line of code:

!!document.createElement('canvas').getContext

Into this line of code:

Modernizr.canvas

This returns a Boolean indicating whether the canvas element is supported on the page. The great thing about using Modernizr as opposed to rolling your own feature detection is that Modernizr is a well-tested, robust and widely used library that does the heavy lifting for you. Twitter, Google, Microsoft and countless others use Modernizr, and you can too. With the ASP.NET MVC 3 Tools Update (released in April 2011) Microsoft even ships Modernizr in the box with new ASP.NET MVC applications. Of course, all I’ve accomplished at this point is detecting whether the <canvas> element is supported. I haven’t said anything about what to do next. Given the knowledge, via feature detection, that a feature is or isn’t available to a browser, a common next step is to create some conditional logic that prevents certain code from executing if a feature doesn’t exist, or executes an alternate path, similar to this:

 

if (Modernizr.canvas) {
  // Execute canvas code here.
}

Adding features to your site based on the presence of additional browser capabilities is referred to as “progressive enhancement,” because you enhance the experience for a more capable browser. At the other end of the spectrum is “graceful degradation,” where the absence of certain features does not cause the browser to error or fail, but rather presents the user with some diminished feature or alternative capability. For older browsers, graceful degradation doesn’t have to be your default option. In many cases, it may not even be your best option. Instead, with the assistance of Modernizr, you can often use one of many available browser polyfills to add HTML5-like features to non-supporting browsers.

What Are Polyfills?

According to the Modernizr Web site, a polyfill is “a JavaScript shim that replicates the standard API for older browsers.” “Standard API” refers to a given HTML5 technology or feature, like canvas. “JavaScript shim” implies that you can dynamically load JavaScript code or libraries that mimic those APIs in browsers that don’t support them. For instance, a Geolocation polyfill would add the global geolocation object to the navigator object, as well as adding the getCurrentPosition function and the “cords” callback object, all as defined by the World Wide Web Consortium (W3C) Geolocation API. Because the polyfill mimics a standard API, you can develop against that API in a future-proof way for all browsers, with the goal of removing the polyfill once support reaches critical mass. No additional work is needed.

By adding a reference to Modernizr on my page, I do get one immediate polyfilling benefit related to the example in Figure 3. The page rendered unstyled because Internet Explorer 8 doesn’t recognize tags like <article> and <header>. And because it didn’t recognize them, it didn’t add them to the DOM, which is how CSS selects elements to be styled.

When I add a <script> tag and reference to Modernizr to my page, the result is a styled page, as in Figure 4. I get this benefit because Modernizr manually adds all of the new HTML5 tags to the DOM using JavaScript (document.CreateElement(‘nav’)), which allows the tags to be selected and styled using CSS.

An HTML5 Page in Internet Explorer 8, with the Help of Modernizr
Figure 4 An HTML5 Page in Internet Explorer 8, with the Help of Modernizr

Beyond its use to add support for new HTML5 elements in Internet Explorer, the Modernizr library doesn’t provide any additional polyfills out of the box. Those you provide yourself, either from your own scripts or from the ever-growing list of options documented on the Modernizr Web site. As of version 2.0, Modernizr provides a conditional script loader (based on yepnope.js—yepnopejs.com) that helps you asynchronously download polyfilling libraries only when needed. Using Modernizr with one or more polyfilling libraries that provide the features you need is a powerful combination.

Using Polyfills to Simulate HTML5 Functionality

In the case of canvas, you can polyfill support for Internet Explorer 8 and earlier with the help of Modernizr and a JavaScript library called excanvas, which adds canvas support at the API level to Internet Explorer 6, Internet Explorer 7 and Internet Explorer 8. You can download excanvas from bit.ly/bSgyNR and, after you’ve added it to your script folder, you can add some code to a script block on your page, as shown in Figure 5.

Figure 5 Using Modernizr to Polyfill Canvas Support

Modernizr.load({
  test: Modernizr.canvas,
  nope: '../js/excanvas.js',
  complete: function () {
    Modernizr.load('../js/html5CanvasLogo.js');
  }
}]);

Here, I’m using the Modernizr script loader to specify three things:

  1. A Boolean expression to test
  2. A path to a script to load if the expression evaluates to false
  3. A callback to run after the check or script loading is complete

In the context of canvas, this is all I need to add some intelligence and polyfilling to my application. Modernizr will asynchronously load excanvas.js only for browsers that don’t support canvas, and then will load my script library for drawing the HTML5 logo on the page.

Let’s look at another example to underscore the value of Modernizr. The detail-oriented among you may have noticed that the site styled in Figure 4 isn’t quite the same as the original page, as rendered in Internet Explorer 9 and depicted in Figure 1. The page, as seen in Internet Explorer 8, is missing a box-shadow and rounded corners, and I couldn’t possibly ship this awesome site without either, so we’ll appeal to Modernizr again for help.

As with canvas, Modernizr can tell me that the CSS3 modules aren’t supported, but it’s up to me to provide a library to polyfill them. Fortunately, there’s a library called PIE (css3pie.com) that provides both modules in a single library.

To add support for border-radius and box-shadow, I can add the code in Figure 6 to my script after I’ve downloaded PIE. This time, I’ll test to see if either the border-radius or box-shadow modules are supported (as opposed to assuming both are always supported or not) and if either is unsupported, dynamically load PIE.js. Once PIE has finished loading, I’ll execute a piece of jQuery to select all of my <article> tags and call the PIE.attach function, which adds support for the border-radius and box-shadow styles already defined in my CSS. The end result is depicted in Figure 7.

Figure 6 Using Modernizr and PIE to Add CSS3 Support

Modernizr.load({
  test: Modernizr.borderradius || Modernizr.boxshadow,
  nope: '../js/PIE.js',
  callback: function () {
    $('article').each(function () {
      PIE.attach(this);
    });
  }
});

CSS3 Support with Modernizr and PIE
Figure 7 CSS3 Support with Modernizr and PIE

Using Polyfills to Aid in Graceful Degradation

In addition to using the polyfilling techniques discussed here, you can also leverage Modernizr to assist in cases where you wish to gracefully degrade your application, as opposed to polyfilling in another library.

Let’s say I have a Bing Maps control on a Web page, and I want to use Geolocation—which we’ll cover in depth in a future article—to find the user’s current location and then place that location as a pushpin on the map control.

While Geolocation is supported in recent versions of all browsers, it’s not supported in older browsers. It’s also a little bit trickier to provide full Geolocation API support purely via JavaScript, and even though polyfills for Geolocation do exist, I’ve decided to instead gracefully degrade my application. In cases where the user’s browser doesn’t support Geolocation, I’ll provide a form she can use to manually enter the location, which I’ll then use to position and pin the map.

With Modernizr, it’s a simple load call to one of two scripts I’ve created, as illustrated in Figure 8. In this case, I’m testing the Modernizr.geolocation property. If true (“yep”), I’ll load my fullGeolocation.js script, which will use the Geolocation API to find my location (with my permission) and place it on a map, as illustrated in Figure 9. If, on the other hand, the test is false (“nope”), I’ll load a fallback script that displays an address form on my page. When the user submits the form, I’ll use the provided address to center and pin the map, as illustrated in Figure 10. This way, my page provides a great experience to modern browsers, while degrading gracefully into a reasonable alternative for older ones.

Figure 8 Using Modernizr to Provide Graceful Degradation

Modernizr.load({
  test: Modernizr.geolocation,
  yep: '../js/fullGeolocation.js',
  nope: '../js/geolocationFallback.js'
});

Mapping with Geolocation
Figure 9 Mapping with Geolocation

Providing Geolocation Fallback Support
Figure 10 Providing Geolocation Fallback Support

It’s easy to look at some of the advanced features of HTML5 and, in the face of a large user base still on older browsers, decide that your site isn’t quite ready for them. But there are great solutions already available that not only aid in graceful degradation, but also provide the capability to bring those older browsers into the present so your users can experience HTML5 right now. Over the course of this article, you’ve seen that with feature detection, Modernizr and polyfilling, it’s possible to adopt HTML5 without delay for the ever-growing segment of your users with a modern browser—all the while being careful not to leave the rest of your user base behind.


Brandon Satrom works as a developer evangelist for Microsoft in Austin, Texas. He can be found on Twitter at twitter.com/BrandonSatrom.

Thanks to the following technical experts for reviewing this article: Damian EdwardsScott Hunter and Clark Sell