OdeToCode IC Logo

Modernizr.js : Feature Detection

Tuesday, October 11, 2011

As we saw in a previous post, Modernizr allows you to work with the new HTML 5 elements, like nav, and still support browsers that don't know anything about a nav, or HTML 5.

However, HTML 5 is more than just a few dozen semantically meaningful elements. There are WebSockets, WebWorkers, WebStorage, and a number of other features that are interesting from a programmability perspective (not to mention all new CSS3 capabilities). The questions is: how do you know if a specific user's browser supports a particular feature?

For years we've relied on browser sniffing (user agent sniffing) to determine browser capabilities, but user agent string are often manipulated, sometimes misleading, and constantly mutating. If you truly want to know if a browser supports a particular feature, like local storage, then the most honest answer you'll receive is by interrogating the browser directly with script.

In other words - if you truly want to know if a browser is a duck, then ask it to quack.

The Tests

When Modernizr loads, it executes a series of tests to see what features the environment supports. For example, here is the test for canvas capabilities:

tests['canvas'] = function() {
    var elem = document.createElement('canvas');
    return !!(elem.getContext && elem.getContext('2d'));
};

There are presently over 40 tests, from applicationcache to webworkers. The result of each test is made available on the Modernizr object for easy access from script. If you want to know if canvas is available, for example, you can just ask Modernizr.

if (Modernizr.canvas) {
    // do something canvasy
}

If you want to see all the available test results, try out my featureDump. This uses underscore and jQuery templates, excerpt below, to grab all the Modernizr test results.

$(function () {

    var featureMap =
        _(_.keys(Modernizr))
            .chain()
            .filter(noFunctionsOrPrivates)
            .map(toNameAndResult)
            .sortBy(name)
            .value();

    function noFunctionsOrPrivates(key) {
          return typeof Modernizr[key] !== "function" &&
                 key.substring(0, 1) !== "_";
    }

    function toNameAndResult(key) {
        return {
            "name": key,
            "result": Modernizr[key]
        };
    }

    function name(feature) {
        return feature.name;
    }

    $("#mainTable").tmpl({ "feature": featureMap })
                   .appendTo($("body"));
});                

The code should produce a table like the following:

image

CSS, Too

In addition to adding test results as properties to the Modernizr object, Modernizr also adds the test results as classes to the root html element. This is a technique used by several libraries to write CSS rules that apply to specific environments. In a live DOM explorer (what you get with Inspect Element in Chrome), you can see these classes. 

image

Notice that if a test passes, you get the test name (canvas). If a test fails, you get a "no-" prefix (as in no-touch). These classes are useful if you want to write CSS style rules that will only apply themselves to browsers that implement (or don't implement) a specific feature.

.no-cssgradients body {
    /* styles */
}

.cssgradients body {
    /* styles */
}

But – What If The Answer Is No?

If a browser does not implement a particular feature – you don't always have to give up. In a future post we'll look at how Modernizr's script loading capabilities can combine with polyfills to bring some old browsers up to speed.