From cacffcd38adc7d5923c47c1051b862ab5eee0aa0 Mon Sep 17 00:00:00 2001 From: Bob Holt Date: Mon, 15 Oct 2012 15:59:54 -0400 Subject: [PATCH] enhance feature & browser detection page - fixes #92 --- .../feature-browser-detection.md | 140 +++++++++++------- 1 file changed, 89 insertions(+), 51 deletions(-) diff --git a/page/code-organization/feature-browser-detection.md b/page/code-organization/feature-browser-detection.md index 8570c00b..0d00210d 100644 --- a/page/code-organization/feature-browser-detection.md +++ b/page/code-organization/feature-browser-detection.md @@ -1,77 +1,117 @@ --- -title: What is Feature Detection -attribution: Connor Montgomery +title: Feature & Browser Detection +attribution: Connor Montgomery & bobholt --- -### What is feature detection? +### Can I Use This Browser Feature? -Feature detection is the concept of detecting if a specific feature is available, instead of developing against a specific browser. This way, developers can write their code for two cases: the browser **does** support said feature, or the browser **does not** support said feature. +There are a couple of common ways to check whether or not a particular feature is supported by a user's browser: -Developing against specific features, instead of developing against a specific browser, not only clears up the peripheral logic of your application, but also makes your job easier as a developer, for several reasons. +* Browser Detection +* Specific Feature Detection -### User-agent testing +In general, we recommend specific feature detection. Let's look at why. -Without getting into too many specifics, there are two reasons why User-Agent testing (developing against a specific browser) is looked down upon, instead of checking to see if a specific feature is supported: they are inconvenient and unreliable. +### Browser Detection -First, let's take a look at a User-Agent string (you can see yours by running `navigator.userAgent` in a JavaScript console). This is Chrome 18's User-Agent string on Mac OS X Lion: +Browser detection is a method where the browser's User Agent (UA) string is checked for a particular pattern unique to a browser family or version. For example, this is Chrome 18's UA string on Mac OS X Lion: - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.142 Safari/535.19 +``` +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.142 Safari/535.19 +``` + +Browser UA detection may check this string for something like "Chrome" or "Chrome/18" or any other part the developer feels identifies the browser they intend to target. + +While this seems to be an easy solution, there are several problems: + +#### Other browsers other than your target may have the same issue. + +If we target a specific browser for different functionality, we implicitly exclude any browser we did not account for. This is also not future-proof. If the browser we target receives a bug fix or change, we may not be able to discern between a 'working' and 'non-working' UA string. We may also need to update our test for each new release. This isn't a maintainable solution. + +#### User Agents are unreliable. + +User Agents are set by the client browser. In the early days of the web, browsers would mimic each others' UA strings in order to bypass exactly this type of detection. It is still possible that a browser with very different capabilities may mimic just the portion of the UA string you're targeting. + +The UA string is also user-configurable. While the user may change this string, the browser's feature support remains the same. -I think it goes without saying that User-Agent strings don't look very easy to work with. On top of that, if we were to use the User-Agent as the base case for our code, we would most likely have to double-check the functionality, or refactor our code when there was a browser update or a new browser. This is especially true now that Chrome and Firefox have a rapid release schedule. +In general, we do not recommend UA string-based feature detection. -The second (and more important) reason we recommend you not to use User-Agent testing is becuase it can be easily modified by the client, and it is **not very reliable at all**. This unreliability is due to historical reasons: as more early browsers were created, their authors would dishonestly identify their new browser by returning an incorrect User-Agent string when executing `navigator.userAgent`. It's also user-configurable, so we really cannot trust this. +### Specific Feature Detection + +Specific feature detection checks if a specific feature is available, instead of developing against a specific browser. This way, developers can write their code for two cases: the browser **does** support said feature, or the browser **does not** support said feature. + +Developing against specific features, instead of developing against a specific browser, not only clears up the peripheral logic of your application, but also makes your job easier as a developer. + +We recommend specific feature detection over UA string detection. + +Now how would you go about doing that? ### How to go about feature detection -There are several ways to go about feature detection. Let's take a look at how to check whether or not a `` element exists in a specific browser, without using a helper library: +There are several ways to go about feature detection: +* Straight JavaScript +* $.support +* A Helper Library + +#### Straight JavaScript + +Let's take a look at how to check whether or not a `` element exists in a specific browser, without using a helper library. We do this by specifically querying whether the method or property exists: ```js -// code taken from Michael Mahemoff's article on feature detection -// on HTML5Rocks: http://www.html5rocks.com/en/tutorials/detection/index.html +// We want to show a graph in browsers that support canvas, but a data table in browsers that don't. +var elem = document.createElement('canvas'); -// we can create this helper to detect whether the tag is available or not +if ( elem.getContext && elem.getContext('2d') ) { -function hasCanvas() { - var elem = document.createElement('canvas'); - return !!(elem.getContext && elem.getContext('2d')); -}; + showGraph(); -// now we can use that knowledge within our application +} else { -hasCanvas() ? showGraphWithCanvas() : showTable(); + showTable(); + +} ``` -Now, let's walk through this chunk of code piece by piece. +This is a very simple way to provide conditional experiences, depending on the features present in the user's browser. We can extract this into a helper function for reuse, but still have to write a test for every feature we're concerned about. This can be time-consuming and error-prone. -```js -function hasCanvas() { - var elem = document.createElement('canvas'); - return !!(elem.getContext && elem.getContext('2d')); - }; -``` +What if someone else wrote all of that for us? -Here, we're creating a helper called `detectCanvas` that returns `true` or `false`, depending if the browser is able to perform a `getContext` on a `canvas` we create. This second line is imperative, because browsers let us create DOM elements it doesn't quite recognize. It's our job to make sure it knows what to do with them, and that's why we use the `getContext` method on the canvas to return true or false. +#### $.support -```js -hasCanvas() ? showGraphWithCanvas() : showTable(); -``` +jQuery performs many tests to determine feature support to allow cross-browser use of many of the features we've come to love. jQuery's internal feature detection can be accessed through [jQuery.support](http://api.jquery.com/jQuery.support/). + +However, we do not recommend this for general use. As the API page says: + +> Since jQuery requires these tests internally, they must be performed on every page load. Although some of these properties are documented below, they are not subject to a long deprecation/removal cycle and may be removed once internal jQuery code no longer needs them. -This line of code will execute the function `detectCanvas`, and depending on if that returns a `true` or `false`, it will execute `showGraphWithCanvas` and `showTable`, respectively. +This detection may be removed from jQuery without notice. That's reason enough not to use it. What other options do we have? -As you can tell, this is a very simple way to provide conditional experiences, depending on the features present in the user's browser. +#### A Helper Library -While writing our own feature detection tests is great, it can be time consuming and error prone. Thankfully, there are some great helper libraries (like [Modernizr](http://modernizr.com)) that provide a simple, high-level API for determining if a browser has a specific feature available or not. +Thankfully, there are some great helper libraries (like [Modernizr](http://modernizr.com)) that provide a simple, high-level API for determining if a browser has a specific feature available or not. -For example, utilizing Modernizr, we are able to do the same canvas detection test with this line of code: +For example, utilizing Modernizr, we are able to do the same canvas detection test with this code: ```js -Modernizr.canvas ? showGraphWithCanvas() : showTable(); +if ( Modernizr.canvas ) { + + showGraphWithCanvas(); + +} else { + + showTable(); + +} ``` -So, while that syntax is great, there are a few problems with this approach. First, it can end up being quite cumbersome to have several conditionals. Secondly, we're sending extra data over, regardless if we'll need it or not. +That's it. Easy. -The Modernizr object exposes a `load()` method that many prefer over the syntax mentioned previously. This is due to the awesomeness of [yepnope](http://yepnopejs.com/), and actually utilizes yepnope internally. Testing for canvas now becomes something like this: +### Performance Considerations + +So, while the Modernizr syntax is great, it can end up being quite cumbersome to have several conditionals. Secondly, we're sending the code for both conditions to every browser, regardless if we'll need it or not. + +The Modernizr object exposes a `load()` method that many prefer over the syntax mentioned previously. This is due to the another library that Modernizr now uses internally: [yepnope](http://yepnopejs.com/). Testing for canvas can now become something like this: ```js Modernizr.load({ @@ -81,25 +121,23 @@ Modernizr.load({ }); ``` -Using the `load` method allows us to send only the required polyfills and code over the wire. I think it is extremely useful because developers can pass an array of objects as an argument to `.load()`, and it will serve the correct files to the correct audience. +Using the `load` method allows us to send only the required polyfills and code over the wire. You can also pass an array of objects as an argument to `.load()`, and it will serve the correct files to the correct audience. -### Tools commonly used to aid in feature detection +Additionally, Modernizr has a [production build configurator](http://modernizr.com/download/) that allows you to specify exactly what parts of Modernizr you want to include and exclude the parts you don't. -Below is a list of commonly used tools that aid in the feature detection process. +### Other Resources + +#### Feature Detection Tools - [modernizr](http://modernizr.com/) - conditionally check to see if a specific feature is available in a browser -- [yepnope](http://yepnopejs.com/) - conditional polyfill loader - [html5please](http://html5please.com/) - use the new and shiny responsibly - [html5please api](http://api.html5please.com/) - an API you can query to see how good (or bad) support for a specific feature is. - [caniuse](http://caniuse.com/) - browser compatibility tables for HTML5, CSS3, SVG, etc… +- [yepnope](http://yepnopejs.com/) - conditional polyfill loader -### Feature detection & jQuery - -jQuery exposes two objects that help developers know about the user's environment (although one has been deprecated): `$.support` and `$.browser` (which has been deprecated). Many developers mistakenly believe using `$.support` is sufficient for detecting the support of a specific feature. Actually, jQuery uses `$.support` only for support tests **jQuery** needs to function properly (for example, the box model). - -With that being said, it is recommended that you utilize a proven method of feature detection, such as [Modernizr](http://modernizr.com). - -### Other resources to check out re: feature detection +#### Helpful Articles +- [Browser Feature Detection](https://developer.mozilla.org/en-US/docs/Browser_Feature_Detection) +- [Using Modernizr to detect HTML5 and CSS3 browser support](http://www.adobe.com/devnet/dreamweaver/articles/using-modernizr.html) - [polyfilling the html5 gap](http://addyosmani.com/polyfillthehtml5gaps/slides/#1) by Addy Osmani -- [feature, browser, and form factor detection](http://addyosmani.com/polyfillthehtml5gaps/slides/#1) by Michael Mahemoff +- [Feature, Browser, and Form Factor Detection: It's Good for the Environment](http://www.html5rocks.com/en/tutorials/detection/index.html) by Michael Mahemoff