Thursday, October 11, 2012

Website Design: A beginner/designers guide to using Modernizr to solve cross-browser challenges

guide to using Modernizr

This piece is intended to help users who are comfortable with HTML and CSS but not so confident using JavaScript. I'll (hopefully) demonstrate how you can use the incredible open-source Modernizr JavaScript library to solve cross-browser design challenges and conditionally load assets (CSS or JS files) based on a number of tests. If the thought of writing JavaScript makes you shudder. Don't worry, it's going to be OK...

If you're looking for some more advanced documentation on Modernizr, there are a few great tutorials out there. Besides Modernizr's own documentation, I recommend this one at NetTuts covering scipt loading alongside the yepnope home page (the microlibrary Modernizr uses for the conditional loading side of things).

Before we start we'll need two things: the latest jQuery and the latest Modernizr. For reference, the info below is based upon v2.5.3 of Modernizr. I am assuming you know how to download and link these files in the head section of your page. It's also worth pointing out that it's best to include Modernizr after all stylesheets have been included but NOT at the bottom of your page (if using the HTML5 shim, it needs to create the elements before styles are applied).

Introduction

If you build websites, you've probably heard of Modernizr. However, many people don't "get it" at first (I was one of them, hence this post). As the project has matured, there are now more benefits to using Modernizr than ever before. However, many people make the assumption that they can just add it to their page and it will fix things. This in only partly true. If you choose the production build of Modernizr and want to add support for HTML5's elements (section etc) in IE 6,7 and 8, choose the "html5shiv" option under Extras. This will then "enable" Old IE to understand and render these elements when Modernizr is included. But the rest of the fixing is for you to do...

What Modernizr does

By default, Modernizr "feature tests" the browser. This is a very important and essential concept to grasp. Out of the box, that's what it aims to do. It essentially finds out what features and capabilities the browser has and then tells you. Imagine the scene:

>Modernizr: "Oh, hi browser. How’s it going?"
Browser: "I got a head full of cache but other than that I’m good. I'm here to display your content."
Modernizr: "OK, fine. What features have you got?"
Browser: "Features?"
Modernizr: "Yes, you know, can you show my text shadows?"
Browser: "Please! ‘course I can..."
Modernizr: "Box shadows?"
Browser: "Eh?"
Modernizr: "I've got some nice subtle CSS3 shadows to rock here with alpha transparency, think you can handle that?"
Browser: "Sure, do I look like Internet Explorer?"
Modernizr: "Fine. How about my background gradients?"
Browser: "Erm... background what?"
Modernizr: "Gradients..."
Browser: (muffled) "No..."
Modernizr: "What?"
Browser: "No."

(PAUSE)

Modernizr: "That’s fine, there’s a fallback. It’s only a solid HEX colour but it will render just fine."

(ANOTHER PAUSE. BROWSER LOOKS AT HIS STATUS BAR)

Browser: "You won’t tell IE9 about this will you?"

And on and on it goes, finding out what a browser can handle and what it can't. It provides the answers to you like this, as a series of classes added to the html tag of the page (you'll need Firebug or similar Development tools in your browser):

<html class=" js no-flexbox no-touch rgba hsla multiplebgs backgroundsize borderimage borderradius boxshadow textshadow opacity cssanimations csscolumns cssgradients no-cssreflections csstransforms csstransforms3d csstransitions fontface generatedcontent video audio svg inlinesvg" lang="en"></html>

If a browser doesn't supports the feature, it is listed with the no- prefix, such as no-touch in the example above (telling us that the browser doesn't support touch input). If it does support the feature, it is added sans the no- prefix.

Simple things first: alternative styles with CSS

Because of the extra classes that Modernizr has added, we now have a means of adding fallbacks through CSS. For example, suppose we were using CSS3 @font-face rules to add custom typography to our page. Perhaps our headline style looked like this:

@font-face {
    font-family: 'CustomFont';
    src: url('fonts/CustomFont.eot');
    src: url('fonts/CustomFont.eot?#iefix') format('embedded-opentype'),
         url('fonts/CustomFont.woff') format('woff'),
         url('fonts/CustomFont.ttf') format('truetype'),
         url('fonts/CustomFont.svg#IcoMoonRegular') format('svg');
    font-weight: normal; font-style: normal;
}

h1 {
    font-family: CustomFont;
    font-weight: 400; font-size: 2em;
}

However, browsers that don't support @font-face will ignore/not understand the custom font and substitute a web-safe font (such as Verdana or Arial) instead. However, the web-safe font might look completely odd at a size of 2em (although the browser doesn't understand the font, it still understands the size declaration). Therefore, we can set a fall back:

.no-fontface h1 { font-size: 1.2em; }

We use CSS descend-ency/specificity to override the "standard" sizing for browsers that need a different rule.

This is just a simple example but hopefully that illustrates how straight away Modernizr can solve some cross-browser challenges. Bottom line: now, instead of thinking in terms of different browsers (and adding merely IE specific hacks for example) you can start to think in terms of browser features. It’s a far more future-proof way of styling a site.

Do some things, depending on some things

If you're even a little comfortable with JavaScript or jQuery, Modernizr also enables you to perform actions based upon the same tests. For example, let's suppose that if a browser supports media queries, I want to add a div at the end of the page telling users that’s the case. Include another JavaScript file in your page (for example, script.js) and include the following (this is jQuery but you can use other JS if you understand it better):

$(document).ready(function(){
    if(Modernizr.mq('only all')) {
        $( '<div>You have media query support, aren't you clever</div>' ).appendTo( 'body' );
    }
});

Now, the JavaScript essentially says, "if you understand media queries, add this div to the end of the body tag".
You can use the same principle to do things based upon the results of any of the Modernizr tests. Modernizr's own documentation gives an if/else illustration for touch:

if (Modernizr.touch){
   // bind to touchstart, touchmove, etc and watch `event.streamId`
} else {
   // bind to normal click, mousemove, etc
}

It's essentially saying, "if you understand touch, do this, otherwise, do this other thing".

Conditional loading with Modernizr

A more recent addition to Modernizr is the ability to conditionally load assets (JavaScript or CSS files) dependent upon the results of Modernizr tests. In a world of multiple devices, and mobile first responsive designs, this makes Modernizr indispensable.
For example, in days gone by we had to stick JavaScript in our code to fix problems that the browser may or may not need. Modernizr overcomes this issue. Here’s a typical example. Let’s suppose we have built a responsive website and we want Old IE to understand and react to our media queries. Old IE (v6, v7 and v8) doesn’t understand media queries by default so we can add Scott (genius) Jehl's excellent respond.js polyfill to fix things up. However, rather than just stick it in the head of our page for all browsers to download, we can load it only if the browser actually needs it. Here's how:

Modernizr.load([{
    // The test: does the browser understand Media Queries?
    test : Modernizr.mq('only all'),
    // If not, load the respond.js file
    nope : '/js/respond.js'
  }
]);

This way Modernizr tests the browser and the asset (in this case a JavaScript file) is loaded only when needed.

It can do more

Modernizr also let's you load assets in other ways and then perform actions after they have loaded. Consider another example. Suppose we want to add a carousel effect to our page. We can do it with CSS3 for browsers that support it and provide a JavaScript solution for those that don't.

Modernizr.load([{
    // Do you understand CSS animations?
    test: Modernizr.cssanimations,
    // If you do, load this file
    yep: {
        'animations': '/css/animations.css'
    },
    // if not, load this JavaScript file AND this CSS file
    nope: {
        'flexneeded': ['/js/flex.js', '/css/flexslider.css']
    },
    // Now, if you are the nope response, once you've loaded those, do this:
    callback: {
        'flexneeded': function(url, result, key) {
            jQuery(function() {
                jQuery('.flexslider').flexslider();
            });
        }
    }
}]);

A few things to note above:

  • for the "nope" response I asked it to load two files using an array ['/js/flex.js', '/css/flexslider.css'] – the syntax is to enclose the array of files (add as many as you need) in square brackets and separate each with a comma.
  • I named the two responses (yep as "animations" and nope as "flexneeded") so I could perform an action specific to one of them. In this instance, I want to kick off the slider in the jQuery example after the files have loaded. Using the callback: along with the response name (line 14 above) means that piece of code only executes based upon that option.

The other option you can consider using is complete:. This code executes once all the files have downloaded, regardless of whether they were yep: or nope:.

For example:

Modernizr.load([{
    // Test
    test: Modernizr.cssanimations,
    // If yes:
    yep: {
        'yesResponse': '/css/yes.css'
    },
    // If no:
    nope: {
        'noResponse': ['/js/yes.js', '/css/yes.css']
    },
    // If, no, once you've loaded the files, do this:
    callback: {
        'noResponse': function(url, result, key) {
            console.log('nope all done for you Sir');
        }
    }
    // No matter what, do the following once everything else has loaded and executed
    complete: {
        console.log('I'm showing this response in the console because all files and actions are done');
    }
}]);

In the example above, I've just added a line to display in the console but you could choose to do whatever you wanted.

Conclusion

If you've read this far, I hope this little "whistle-stop" look at Modernizr has proved useful. For non programmers, it can appear intimidating and if that seems the case, perseverance will reward you with one of the most useful and versatile front-end developers tools you could hope for.