On Github phette23 / Modernizr-Presentation
Eric Phetteplace Emerging Technologies Librarian Chesapeake College
We want to use modern features today but they're not supported in every browser
We all know HTML5 & CSS3 are awesome. So we go and design a beautiful site. It looks amazing; everything has a gradient, our buttons wiggle with animations. It pops up an alert every time the user sneezes. It tells them the room temperature and recommends an appropriate drink. But then...we accidentally open it in IE 8. & it's awful; border backgrounds don't show, gradients are crushed to flat colors, nothing's animated, it spews errors because our scripts are accessing objects that don't exist. What do we do to get around this?if ( navigator.userAgent.match(/Android 2\.[0-3]/) ) { oopsNoInlineSVG(); oopsNoContentEditable(); // no need to run oopsNoCssGradients(); }User Agent sniffing is better than IE-specific approaches in that it works for almost any browser. The basic method is to detect the User Agent and then make tactical decisions based upon that info; here in this example I test navigator.userAgent to see if it might be Android 2.0 to 2.3 and then execute a couple fallback functions which fill in support for things like SVG & contentEditable. There are a couple fundamental issues with UA sniffing. Most importantly, it presents a maintenance challenge. As new browsers appear & features are added or altered, UA string lists need to be updated. I would need several blocks of code like this, each one crafted to the particular abilities of the browser I'm trying to support. While there are projects that try to track device UAs & whether or not they're considered "mobile,"" I'm not sure there's a comprehensive database of UA strings paired with features. Secondly, UA strings are not a reliable source of information; they can be spoofed & new browsers tend to mimic old ones precisely to work around poorly written UA sniffing. "New browsers" may not seem like a big deal—I mean, how often does a new browser come on the scene? Google Chrome was the last one & we may not see another for awhile. But consider all the mobile apps which use a webkit browser window; any of those might choose its own UA string. The Twitter app on iOS, for instance, uses a webkit UA string but with "Twitter" appended to it. While Twitter smartly builds on the traditional webkit UA string, other apps might not be so forgiving.
a.k.a. "ignoring older browsers"
Well, an increasing number of sites opt for the "do nothing" approach of simply using modern features without any fallback, knowing that sites will break in older browsers. >>>STEP>>> Google Drive is the most prominent example I've seen lately; it prompts us to upgrade our browser not only in IE8 but even in versions of Firefox that are a few updates behind. There are some situations where employing features without fallbacks is probably fine; border-radius is a great example of when the complication of doing a fallback outweighs its value. So our buttons are rectangular, who cares? As long as the experience is still quality then users will be OK. But many sites aren't just dropping a few rounded corners, they're leaving out _functionality_ as well. It's interesting that this is happening in the midst of the great Responsive Web Design paradigm shift that says all devices deserve a decent experience no matter their screen width. Whether or not you think RWD is the right approach all the time, it's inarguably in the spirit of the web; open access to information. & not doing anything & cutting off users is a step backwards. Since we have the means nowadays—& I'm thinking not only of media queries & JavaScript libraries like Modernizr, but a general development philosophy—it's never been more irresponsible to let a site break for certain users.One of the web's most popular JavaScript libraries
Surprise, surprise, Modernizr is the solution we're looking for, especially in situations like the one I've been talking about where we are using a few new features that have variable support across multiple browsers. As keeping up with devices & browsers get more difficult, & as more & more sites decide that HTML5 & CSS3 are too good to pass up, Modernizr has been growing in popularity. Its usage has almost tripled in the past year in these statistics from BuiltWith.com. It is used by almost 6% of the top 10,000 sites on the web. So what exactly is Modernizr? It's a JavaScript library that allows you to branch your site's appearance based on browser capabilities, providing an easy opportunity to fallback when necessary. In essence, you write CSS or JavaScript rules which say "if this feature is available, do X; otherwise, do Y." It's important to note that Modernizr *is not* a user-agent detection library. It detects browser support for particular features but doesn't care about the browser itself. It's also very lightweight & carries no dependencies; you don't need to include a monolithic library like jQuery in order to run it. In fact, Modernizr is great for web designers who don't want to bother with any other JavaScript. Overall, it's the perfect tool for using HTML5 & CSS3 in a responsible manner without too much overhead..no-inlinesvg .svg-wrapper { background-image: url(fallback.png); }
if ( navigator.userAgent.match( hugeListOfUAsThatDontSupportSVG ) ) { $( 'div > .svg-wrapper' ) .css( { "background-image": "url(fallback.png)" } ); }
index.html
<html class="no-js"> <head> <title>Awesome Website</title> <script src="modernizr.test.js"></script> </head> ...Enough preaching, let's get down to modernizing. To start, we do just 2 things: >>>STEP>>> we add a class of "no-js" to the HTML tag, and >>>STEP>>> include the Modernizr script tag in our head. The first step relates to how Modernizr functions; it adds classes to the HTML element. But what if JavaScript is disabled & Modernizr cannot execute? Then the "no-js" class gives us with a little information; we now know that JavaScript is unavailable &, furthermore, that we can't run our feature detection tests to see what else _is_ available. Even when it can't execute, Modernizr is gives us a hook to enable fallbacks. While you usually want to load scripts down at the very bottom of the page to avoid blocking, Modernizr needs to be at the top because many of our styles will depend upon its results. If we put it at the bottom, our page might suffer from a "flash of unstyled content" as content loads without background images, Modernizr adds a slew of classes, & then suddenly a bunch of images snap into place. ↓↓↓STEP DOWN↓↓↓
var docElement = document.documentElement; docElement.className = docElement.className.replace(/(^|\s)no-js(\s|$)/, '$1$2') + ' js'; var div = document.createElement('div'); div.innerHTML = '<svg/>'; if (div.firstChild.namespaceURI == "http://www.w3.org/2000/svg") { docElement.className += ' inlinesvg'; } else { docElement.className += ' no-inlinesvg'; }Here's my own pseudo-Modernizr, to give you an idea of the library's internal mechanics. I'll walk through this but you don't have to understand the specifics of each expression to get the underlying idea. First, Modernizr finds the root element—in most cases our HTML tag, but its written in such a generic manner that this would work on XML documents—& it replaces the "no-js" class with "js." Why? Well, if Modernizr is running then clearly JavaScript is enabled, so it's providing us with this first piece of info. Then Modernizr runs a series of feature tests and adds a certain class to the HTML tag depending upon the result of the test. Here's an example test; it borrows a little from Modernizr but isn't identical. Basically, we create a div & put an svg tag in its innerHTML. If that tag has the SVG namespaceURI, then SVG is supported & we add the "svg" class to the HTML tag. If not, we add the no-svg class.
index.html
<html class=" js inlinesvg"> <head> <title>Awesome Website</title> <script src="modernizr.svg.js"></script> </head>
style.css
.inlinesvg svg { display: block; } .no-inlinesvg .svg-wrapper, .no-js .svg-wrapper { background: url(fallback.png) no-repeat; }
Let's see an example.
The end result is just a different set of classes on the HTML element. If the browser supports inline SVG, we'll get an "inlinesvg" class on the HTML tag; if not, it'll be "no-inlinesvg." The class names are always formatted this way; either "feature name" or "no hyphen feature name," so you never have to think too hard about what to use in your CSS selectors. >>>STEP>>> These two selectors cover all our possible situations. If SVG is available, the first rule just makes sure that our SVG element is displayed. If we don't have SVG around, we put up a background-image in its place. This image could just be the PNG export from Inkscape or Adobe Illustrator of our SVG file. Finally, if JavaScript is disabled & Modernizr didn't execute we can't be sure about the browser capabilities so, just to be safe, we use the background image. I hope now everyone sees why Modernizr is so excellent. We didn't need to write a line of JavaScript to provide what are essentially two branches of our site's appearance. Browsers now receive an experience appropriate to their capabilities & we don't need to update a User Agent list to keep our fallback in place; this will work forever & as more browsers support SVG our fallback will be used less & less. >>>CLICK>>> So far, I've shown you code samples in my slides but let's see an actual example in a couple different browsers.<html lang="en" xml:lang="en" class=" js flexbox canvas canvastext webgl no-touch geolocation postmessage websqldatabase indexeddb hashchange history draganddrop websockets rgba hsla multiplebgs backgroundsize borderimage borderradius boxshadow textshadow opacity cssanimations csscolumns cssgradients cssreflections csstransforms csstransforms3d csstransitions fontface generatedcontent video audio localstorage sessionstorage webworkers applicationcache svg inlinesvg smil svgclippaths cufon-active cufon-ready">It's important to use a custom Modernizr build that tests just for the features we need. Running the entire Modernizr development build is very bad practice; first of all, it will download a lot of unnecessary bytes as all the tests are included in the library. Secondly, it'll take more time to execute all the tests. Modernizr is a lightning-fast library so this isn't fatal but it's completely avoidable. >>>STEP>>> If we do include the whole library, we'll also end up with dozens of classes strung off our HTML tag. This is an example from an actual, production website that's including the whole library for no particular reason. It's not just that this is the ugliest looking HTML tag I've ever seen, it's that a lot of the feature tests add rather generic classes that might be used elsewhere like "audio", "video," "canvas," and "history." If we have a large team working on our website, everyone would need to be aware that these are unsafe class names. Incidentally, the Modernizr download builder lets us choose to prefix all the class names, a great option if we're uncertain that someone hasn't already used on these generic classes somewhere on our site. So if we are going to be ridiculous & include the entire library, we can make every class bear prefix "mod-" to reduce conflicts. Let's take a look at the download builder now.
Modernizr.load( { test: Modernizr.geolocation, nope: "geolocation.js" } );
advanced Modernizr.load()
Modernizr.load([{ test : Modernizr.fontface && Modernizr.canvas && Modernizr.cssgradients, nope : [ 'presentational-polyfill.js' , 'presentational.css' ] } , { test : Modernizr.websockets && window.JSON, nope : 'functional-polyfills.js', both : [ 'app.js', 'extra.js' ], complete : function () { myApp.init(); }, } , 'post-analytics.js' ]);
Let's see a more complex example.
Modernizr comes with a built-in script loader which we can use to seamlessly load our polyfills based upon the results of its tests. Essentially, we specify a Modernizr test & then, if it's false, the resources listed next to "nope" are loaded. >>>STEP>>> It can get a lot more complex than that, in fact Modernizr.load is its own separate JavaScript library known as yepnope. We can do some pretty complex things, from specifying multiple tests, to loading arrays of resources, to executing a function upon completed loading. I personally have yet to come across a use case that's as complex as this one, which I stole from the Modernizr documentation, but if for a full-fledged HTML5 app this sort of routine would be fairly common. >>>CLICK>>> Let's look at a more sophisticated example. We're going to polyfill some of my favorite HTML5 features; the incredibly useful new form types & attributes..no-inlinesvg.no-border-image.no-url-data-uri { dont: dothis; }Modernizr puts you in good shape but it's important to understand what it will _not_ do: Modernizr doesn't write our fallbacks for us, it just provides hooks so that we know when to employ them. On a complicated site with lots of modern features, we need to make sure that any combination of missing features still yields a good experience. >>>STEP>>> We can easily overuse Modernizr. If we find ourselves writing fallbacks like ".no-inlinesvg.no-border-image.no-url-data-uri" then we may want to revisit how complex and maintainable our code is.
Eric Phetteplace@phette23phette.net