useHTML5&CSS3today  withModernizr – Using Modernizr – Modernizr is not a Superhero



useHTML5&CSS3today  withModernizr – Using Modernizr – Modernizr is not a Superhero

0 1


Modernizr-Presentation

Presentation at Amigos HTML5/CSS3 on Modernizr.

On Github phette23 / Modernizr-Presentation

useHTML5&CSS3today  withModernizr

Eric Phetteplace Emerging Technologies Librarian Chesapeake College

@phette23phette.netgithub.com/phette23

The Problem

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?

Approaches

  • IE Conditional Comments
  • User Agent Sniffing
  • Live & Let Die
We have a few tools to help us with this conundrum. If you've worked on the web for a fair amount of time, you're probably familiar with all of these techniques. I want to briefly review them but also note how they fundamentally fall short.

I'm a web developer; I'll blame IE!

So HTML5 & CSS3 are hardly the first new features to appear on the scene with spotty browser support. No web conference is without its IE jokes; I got mine in on the second slide. We've been working around IE quirks for decades now and we have a long history of tools to do so, from the star hack to IE conditional comments.

Your Argument is Invalid

But these approaches were designed to deal with IE running on desktop browsers. Our fundamental issue isn't IE's lack of support, it's variability between browsers. Let's say we create a site that simply must have support for CSS gradients, the contentEditable attribute, and inline SVG. Quick question: will it work in Android Browser 2.3? Yes or no, just does the browser support all three features. Answer: No, it supports CSS gradients but not inline SVG & CE. OK a 2nd question: what about iOS Safari 4.3? Answer: Nope, same support (yes to gradients, no to SVG & CE). It's not just these browsers either: Opera Mini supports none of these three, IE8 and 7 support contentEditable but not gradients or SVG, & IE9 still doesn't have gradients. If we care about supporting as many browsers as possible, we'll need a more generic means of providing fallbacks, one that can be crafted to suit different combinations of feature support.

User Agent Sniffing

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.

Did I Not Just Show You This Slide?

So we've determined that UA sniffing doesn't solve our fundamental problem. Even if we're now prepared for IE and Android with our custom scripted fallbacks for each; what about the thousands of other browser versions out there and the various devices they run on?

Live & Let Die

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.

Enter Modernizr

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.

With Modernizr

.no-inlinesvg .svg-wrapper {
  background-image: url(fallback.png);
}

Without Modernizr

if ( navigator.userAgent.match( hugeListOfUAsThatDontSupportSVG ) ) {
  $( 'div > .svg-wrapper' )
  .css( { "background-image":
  "url(fallback.png)" } );
}
If _what_ Modernizr does is great, _how_ it does it is also wonderful. Part of the reason why Modernizr is so successful, in my opinion, is the brilliance of its API. It's both quite simple so we don't have to know a lick of JavaScript to employ it, but also provides advanced hooks for more sophisticated usage. Essentially, we provide fallbacks by writing CSS rules for the features that aren't available; if inline SVG isn't available, this no-inlinesvg rule will kick in, replacing our SVG image with a png that all browsers can handle. On the right I'm trying to do the same thing but with JavaScript. >>>STEP>>> I have to rely on the user agent, then check it against a huge regular expression of browsers which I know don't support SVG. Every time a new browser appears, I must go back & update my huge list of user agent strings.

Using Modernizr

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↓↓↓

Pseudo-Modernizr

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.

Afterwards...

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.

There is no single Modernizr.js

<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.

Choices, Choices...

Modernizr provides dozens of tests. Here we're selecting only our CSS gradients and inline SVG because that's all we need. We'll get a nice, trim build that only downloads & runs the code for those two tests. The default build screen shows a few sections for CSS3, HTML5, and miscellany like SVG support. But there's a huge sub-section under that "Non-core detects" drop-down which includes tests for things like contentEditable & JSON. If we can't find what we're looking for initially, chances are it's one of the non-core detects and Modernizr will still be able to help us out. Finally, it's worth noting that by default Modernizr includes the HTML5 Shiv, a short script that lets you style the HTML5 elements like article, aside, & section in Internet Explorer.

Polyfills

Polyfills are JavaScript libraries that replace modern features. Essentially, they emulate or replace missing functionality so that users in older browsers have a comparable experience. Modernizr is designed with polyfills in mind, because a lot of the time a mere CSS fork is not enough to preserve the user experience. My examples before were a bit easy—background images easily filled in. But no background image on the planet can replace lack of geolocation support, for instance.

Modernizr.load()

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.

Modernizr is not a Superhero

.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.

"The Undetectables"

There are a few niche things that just cannot be detected, either because they're not exposed to JavaScript or behavior is so spectacularly buggy across platforms that even Modernizr can never be sure.

"The Undetectables"

  • Datepicker, Colorpicker
  • Font Rendering
  • position: fixed; on Mobile
  • The Full List
- UI elements to web forms can't be detected, so Modernizr can tell you if the browser is applying built-in validation to a Time or Color input but not if it's offering a nice picker >>>STEP>>> - Font rendering like anti-aliasing, smoothing, & font-weight differences cannot be detected >>>STEP>>> - Position: fixed is technically supported on some recent but current versions of Android & iOS Safari, like Android 2.3 & Safari 5-6, but is very buggy >>>STEP>>> - The Modernizr wiki on Github maintains a complete list

Useful Sites

- haz.io shows us the current browser's abilities in Modernizr, it basically just displays a nice list of the global Modernizr object's contents - Can I Use displays compatibility tables for many features & browsers. It's a great place to go to decide if a feature is mature enough to use at all, or whether you even need to provide a fallback - Similarly, HTML5 Please gives concise advice on which modern features are usable & how you should polyfill lack of support - Finally, the Modernizr wiki lists polyfills for pretty much every features in their "All-In-One Entirely-Not-Alphabetical No-BS Guide to HTML5 Fallbacks"

Thanks!

¿Questions?

Eric Phetteplace@phette23phette.net