Regressive Enhancement with Modernizr and Yepnope

Version 1.0 of Alex Sexton’s yepnope.js script loader was released last week, so I figured it would be a great time to show you how you can combine Yepnope with Modernizr to make use of HTML5 features without incurring extra downloads for users with up-to-scratch browsers.

What is Regressive Enhancement?

You’re probably already familiar with the concept of progressive enhancement: designing a baseline version of a feature that works in all browsers, then adding features for more capable browsers.

The “polyfill” or “regressive enhancement” technique just means that you go ahead and use the new features, then use JavaScript to emulate native behavior in older browsers. So, instead of using a script to give you drop shadows on all browsers, you just write your CSS with the box-shadow property, and then include a script that transparently takes that property and uses the values you specify to create a drop shadow in JavaScript.

What is Modernizr?

For those of you who aren’t familiar with it, Modernizr is a small (3.7KB gzipped) JavaScript library that detects the presence of HTML5 and CSS3 features in the browser. Raena made use of it in her recent tutorial on creating a progressively enhanced image gallery, and Kevin interviewed Paul Irish, one of the library’s creators, in a recent episode of the SitePoint pocast.

Modernizr is ideally suited to regressive enhancement, because it allows you to know when you can rely on browser functionality, and when you need to fall back on JavaScript or alternate styling.

There are two main ways of using Modernizr. The most common way is to rely on the HTML classes it adds to your html tag. When viewing a page with Modernizr in the latest Firefox 4 beta, here’s what I see in the opening <html> tag:

<html class="  js flexbox canvas canvastext webgl no-touch geolocation postmessage  no-websqldatabase indexeddb hashchange history draganddrop no-websockets  rgba hsla multiplebgs backgroundsize borderimage borderradius boxshadow  textshadow opacity no-cssanimations csscolumns cssgradients  no-cssreflections csstransforms no-csstransforms3d csstransitions  fontface video audio localstorage no-sessionstorage webworkers  applicationcache svg inlinesvg smil svgclippaths" lang="en">

All those classes tell me which features are available in the browser. For example, I have @font-face, web workers, and CSS3 box-shadow, text-shadow, and border-image, but I don’t have websockets or 3D CSS transforms. So, in my CSS, I can do something like this:

.borderradius .box {
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;
  border-radius: 5px;
}

.no-borderradius .box {
  // fallback code providing an alternate styling to browsers that don't support border-radius
} 

That’s simple enough. The cool bit is that Modernizr also provides you with a JavaScript API that can tell you whether or not a feature is available, including a few features that don’t show up in the <html> tag classes. For example, let’s say I have some JavaScript code that provides placeholder values for input elements. I don’t need to run this code if the browser supports the placeholder attribute, so I can use Modernizr to check for that before executing my snippet:

if(!Modernizr.input.placeholder) {
 // custom placeholder code
} 

This way, my code will only run if there’s no built-in browser support for placeholder text.

The Problem

There’s still a slight problem here, though. If a browser does support the placeholder attribute, I’m still requiring it to download a bunch of code that does nothing but emulate that attribute’s behavior. I’m sure you’ll agree this is a little wasteful! Enter yepnope.js. Yepnope loads scripts if certain conditions are met. The best part is that it integrates beautifully with Modernizr, so everything just snaps into place.

The simplest example, from the library’s website, looks like this:

yepnope({
  test : Modernizr.geolocation,
  yep  : 'normal.js',
  nope : ['polyfill.js', 'wrapper.js']
}); 

If the browser supports geolocation, that snippet will load the normal.js file from the server; otherwise, it’ll load both polyfill.js and wrapper.js.

A Practical Example

Now that you know how all the parts work, let’s put them together into a real-world example. Let’s say you have a simple signup form, consisting of fields for a username, password, and email address. Here’s the markup:


<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>yepnope.js test</title>
  <style>
    div { margin: 10px; }
    div p { font-size: 12px; color: #333; }
  </style>
</head>
<body>
  <form>
    <div>
      <label for="username">Username</label>
      <input type="text" required pattern="[a-z0-9_-]{3,15}" name="username" id="username">
      <p>Between 3 and 15 characters. Only letters, numbers, underscores (_) and hyphens (-) are allowed.</p>
    </div>
    <div>
      <label for="email">Email</label>
      <input type="email" required placeholder="someone@somewhere.com" name="email" id="email">
    </div>
    <div>
      <label for="password">Password</label>
      <input type="password" required name="password" id="password">
    </div>
    <input type="submit" value="Submit">
  </form>
</body>
</html>

The form uses a number of HTML5 features: the required attribute, the placeholder attribute, and the pattern attribute. In a supporting browser, such as Firefox 4 beta, this provides placeholder text and some basic client-side validation:

The placeholder attribute provides placeholder text

The email input type provides built-in format validation

The pattern attribute provides regular expression based validation

In a non-supporting browser, however, you get big fat nothing. Let’s fix this using a JavaScript-based polyfill along with Modernizr, and Yepnope to load it only when it’s required.

Step 1: Download Modernizr and Yepnope

The new custom Modernizr builder allows you to bundle yepnope right into Modernizr, so let’s do that. Head over to http://modernizr.github.com/Modernizr/2.0-beta/. In this case all we need to detect are the form attributes and input types, so click those two checkboxes, leaving the HTML5 Shim and Modernizr.load (yepnope.js) boxes ticked.

Click Generate, and then Download Build to grab your custom Modernizr library.

Step 2: Include Modernizr in Your Page

Because Modernizr needs to determine if other scripts should be run, and adds classes that might be required by your CSS, it should go at the top of your HTML, rather than at the bottom as is usually recommended:

<script src="modernizr.custom.02401.js"></script> 

(Remember to replace the custom build number with your own.)

Step 3: Test for Feature Support

Now we want to test to see if the browser supports the new input types and attributes:

yepnope({
  test: Modernizr.inputtypes.email && Modernizr.input.required && Modernizr.input.placeholder && Modernizr.input.pattern,
  nope: 'h5f.min.js'
});

We’re loading the H5F library by Ryan Seddon, which emulates all the new input types and attributes we’re using in this example. In this case we have one polyfill script that covers a bunch of different features, so we’re checking for all of them at once and loading the polyfill if even one of them is unsupported. This is not always ideal, but we’re keeping things simple for the sake of illustration.

You’ll also notice we’re not using a “yep” in this yepnope call. That’s fine: in the event all the features we’re detecting for are present, Yepnope won’t do anything at all, which is what we want.

Step 4: Execute Callback Code

You might be tempted to just call your newly included library on the next line of code, but this won’t work. Yepnope.js loads scripts asynchronously, so the browser won’t wait for the script to be finished loading before moving on to the next line in your code. That means that if you try to use the features you’ve just told Yepnope to load, you’ll likely get an error.

Instead, Yepnope allows you to set a callback function for each script you load, to be run once that script has finished downloading. Here’s how that works:

yepnope({
  test: Modernizr.inputtypes.email && Modernizr.input.required && Modernizr.input.placeholder && Modernizr.input.pattern,
  nope: 'h5f.min.js',
  callback: function(url, result, key) {
    H5F.setup(document.getElementById("signup"));
  }
});

The function you specify as a callback will be called each time a script is loaded. This means that if you’ve specified both a yep and a nope, the callback will be called twice. Fortunately, the callback is passed three useful parameters: url is the URL of the result that was loaded, result is a Boolean value representing whether or not your test passed, and key is a way of referring to specific resources using keys (you don’t need to worry about this for now).

In the above example, I’m only loading a script on nope. As a result, the callback will only be called once anyway, and only if the test fails, so I don’t need to worry about the parameters.

Step 5: You’re Done!

Believe it or not, you’re done. With the above code in place, browsers that support the new form features will use their built-in functionality, while older browsers will load a JavaScript fallback. The JavaScript will only be loaded in those non-supporting browsers, so you’re rewarding modern browsers with snappier load times. Even better, because the polyfill hooks onto the new attributes and doesn’t require any extra classes, the solution is future-proof. Fewer and fewer visitors over time will download the polyfill, until eventually none of them do.

What’s Next?

I’ve only covered the simplest use cases of yepnope,js. For such a tiny library, it packs a lot of functionality, so you should definitely have a read through the project’s page to see some more advanced usage examples. Even if you’re not using it for HTML5 or CSS3 polyfills, there are potential performance gains to be had from loading your scripts asynchronously and on demand, so Yepnope is still worth looking into.

Now, all you have to do is start putting new HTML5 and CSS3 features to use on your website, safe in the knowledge that you can provide a full-featured fallback to users of older browsers without impacting the experience of your more up-to-date visitors.

Win an Annual Membership to Learnable,

SitePoint's Learning Platform

  • Abel Tamayo

    Great article. I’ve been following YayQuery all this time but catching up with some of the terms and plugins was a little tricky.
    Thanks for explaining it all so well.

  • Mitchell

    Very interesting article putting the various tools together. Maybe I missed it, but is there a demo where we can see the form in action and can try it on different browser?

    • arnold

      +1 demo would be nice

    • Louis Simoneau

      Here’s a rudimentary demo: http://s3.sitepoint.com/examples/yepnope/index.html

      It’s not doing anything with the validation stuff provided by H5F, so it’s not really a replica of the native functionality as it stands. However, the placeholder works as is, and it’s a demo of the way the script is loaded (or not) with Yepnope.

      • Arkaed

        I took the code from the demo page and played with it for a while. After I removed the required-attribute from the email field something weird happens with older browsers (tested with IE8 and Firefox 3.6.13). When first loading the page there is no placeholder in the email field. But when the field gets focus the placeholder text appears. Any idea why the placeholder needs to have required-attribute with it in order to work with older browsers?

      • Louis Simoneau

        @Arkaed, I’m not super familiar with H5F, I just grabbed it as an example of the kind of polyfill script that you could use in this way, so I’m not sure what the issue would be in that case. Sorry!

  • Dan

    I’m not so sure about using something like Modernizr to control what CSS is applied – what about users of standards-compliant browsers with JavaScript disabled?

    JavaScript-based feature detection within JavaScript apps is a different story.