How to Detect Features Instead of Browsers
Traditionally, many web developers have used browser detection in an attempt to provide a consistent experience between browsers. The typical implementation performs a single comparison operation, usually involving the user-agent string, and then makes several design assumptions about the features supported by that browser. In practice, however, feature detection has proven to be a more effective technique that requires less maintenance. This article shows how to use feature detection to verify support for standards-based featured and demonstrates different ways to detection features effectively.
The Case Against Browser Detection
As typically implemented, browser detection has several drawbacks, including but not limited to the following:
- When a new browser is released or an existing browser is updated, you must factor the new browser into your browser detection code. Updated browsers may support standards and features that were not supported when the browser detection code was designed.
- Conclusions about feature support may not be correct or appropriate.
- As new devices become available, they frequently include new versions of browsers; consequently, browser detection code must be reviewed and potentially modified to support the new browsers. In some cases it becomes more complicated to create customized implementations for each browser.
- A browser detection technique may not accurately identify a given browser. For example, many browsers support the ability to modify the user-agent string.
To illustrate, consider the following (invalid) code sample, which inappropriately attempts to use browser-specific techniques to assign an event handler.
function getInternetExplorerVersion()
// Returns the version of Internet Explorer or a -1
// (indicating the use of another browser).
{
var rv = -1; // Default value assumes failure.
var ua = navigator.userAgent;
// If user agent string contains "MSIE x.y", assume
// Internet Explorer and use "x.y" to determine the
// version.
var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
if (re.exec(ua) != null)
rv = parseFloat( RegExp.$1 );
return rv;
}
function registerEvent( sTargetID, sEventName, fnHandler )
{
var oTarget = document.getElementById( sTargetID );
if ( oTarget != null ) {
// This is not an optimal example; it demonstrates
// techniques that you should not follow.
if ( getInternetExplorerVersion() > -1 ) {
oTarget.attachEvent( "on" + sEventName, fnHandler );
} else {
oTarget.addEventListener( sEventName, fnHandler );
}
}
}
This example attempts to use the user-agent string to determine whether the browser is Windows Internet Explorer. While the example works in Internet Explorer and most other browsers, it contains multiple problems, including but not limited to the following:
- This code sample focuses on the browser (Internet Explorer), not the feature (DOM Level 3 event handler registration). As a result, if Internet Explorer is used to view the webpage, the example uses attachEvent to register the event handlers even if the version of Internet Explorer supports the addEventListener method. In addition, this example would need to be tested with any (hypothetical) new versions of Internet Explorer.
- This example assumes that Internet Explorer does not support the addEventListener method, which is an incorrect assumption with regard to Internet Explorer 9. Many browser detection implementations make similar assumptions.
- While this particular sample does not attempt to distinguish between versions of Internet Explorer, there have been changes in the way Internet Explorer handles the user-agent string. Applications designed according to the behavior of earlier version may report incorrect results for webpages displayed in Compatibility View, which was introduced in Internet Explorer 8. For more information, see Understanding User-Agent Strings and Understanding the Compatibility View List.
This example illustrates many of the weaknesses with browser detection as a technique for determining the features supported by a web browser. A more effective approach is to detect support for the feature directly, as shown in the following code sample.
function registerEvent( sTargetID, sEventName, fnHandler )
{
var oTarget = document.getElementById( sTargetID );
if ( oTarget != null )
{
if ( oTarget.addEventListener ) {
oTarget.addEventListener( sEventName, fnToBeRun, false );
} else {
var sOnEvent = "on" + sEventName;
if ( oTarget.attachEvent )
{
oTarget.attachEvent( sOnEvent, fnHandler );
}
}
}
}
This example provides two improvements over the previous one:
- It focuses on the feature rather than the browser. If the user happens to be using a browser that supports the addEventListener method (as such Internet Explorer 9 and many other browsers), then that method is used to define the event handler.
- It emphasizes the standards-based technique over the proprietary technique. In this case, the sample verifies support for the preferred technique (the addEventListener method) before attempting to use the alternate technique.
This example is effective because it does not assume the behavior of any given browser. The technique does not need to be updated to support any (hypothetical) new versions of Internet Explorer, nor does it need to be expanded to support new browsers or devices. The sample simply focuses on whether the feature is available. This is the main difference between feature detection and browser detection.
In an ideal world, all browsers would support the same standards and implement those standards in precisely the same way. In practice, however, a number of variations exist between browsers and their respective implementation of various standards.
For web developers, the best defense is to rely on features that are defined in widely supported, stable standards, such as HTML5, Cascading Style Sheets, Level 3 (CSS3), Scalable Vector Graphics (SVG), and so on. Detect support for the features that you want to use and provide alternative approaches only if necessary.
There are a number of ways to detect features, including:
- Search for Document Object Model (DOM) objects or properties associated with the feature.
- Attempt to create an object or attribute related to the feature.
- Use the hasFeature method to determine whether the DOM implementation supports the feature.
For more information, see How to Create Effective Fallback Strategies.
Detecting DOM Objects and Properties
The most common way to detect features is to examine the DOM for objects or properties associated with the feature you want to use. For example, the following code sample shows how to determine whether the browser supports the addEventListener method in order to define an event handler.
function registerEvent( sTargetID, sEventName, fnHandler )
{
var oTarget = document.getElementById( sTargetID );
if ( oTarget != null )
{
if ( oTarget.addEventListener ) {
oTarget.addEventListener( sEventName, fnToBeRun, false );
} else {
var sOnEvent = "on" + sEventName;
if ( oTarget.attachEvent )
{
oTarget.attachEvent( sOnEvent, fnHandler );
}
}
}
}
In this example, the event registration code verifies that the object that handles the event provides support for the addEventListener method before calling the method to register the event handler. This avoids runtime syntax errors and helps provide an alternative approach (also called a fallback strategy) if the preferred technique is not supported.
To determine whether there are objects, properties, attributes, or methods that help you detect a feature, refer to the specification that defines the feature you want to use.
For example, Internet Explorer 9 supports the performance object of the Navigation Timing specification. As of this writing, this specification defines the "window.performance" attribute. As a result, you can use the presence of a performance property on the window object to determine whether the current web browser supports the Navigation Timing specification, as shown in the following code sample.
if ( window.performance ) {
showLoadTimes( window.performance );
}
In this example, the showLoadTimes()
function is called only when the window object supports a performance property.
Creating Objects to Support Features
Some features cannot be detected in the DOM until they are rendered by the browser. For example, Internet Explorer 9 supports the audio element only when a webpage is displayed in IE9 Standards mode. If a webpage containing an audio element is displayed in IE5 (Quirks) mode (or displayed by an earlier version of Internet Explorer), the audio element is rendered as an unknown (generic) element.
Based on the previous section, the following code sample might seem sufficient to determine whether a browser supports the audio element.
<!doctype html>
<head>
<title>Simple Audio Support Test</title>
<script type="text/javascript">
function supportsAudio()
{
var d = document;
var o = d.getElementById( "audio1" );
return ( o != null );
}
function checkAudioSupport()
{
var s = ( supportsAudio() == true ) ?
"supports " : "does not support ";
var o = document.getElementById( "dOutput" );
o.outerText = "Your browser " + s + "the audio element.";
}
</script>
</head>
<body>
<audio id="audio1" src="audiofile.mp3" />
<button onclick="checkAudioSupport();">
Click to test audio support.
</button>
<br />
<div id="dOutput">Please click a button</div>
</body>
</html>
This example tries to create an audio element and bases its feature support evaluation on whether the object was created. While this example would correctly indicate that Internet Explorer 9 supports the audio element when the page is displayed in IE9 mode, it would incorrectly make the same claim when the same webpage is displayed in IE5 mode. The supportsAudio()
function presumes that the ability to get a reference to an object created by an audio element is, in fact, an audio element that supports music playback. This is a problem because web browsers are supposed to create generic references to HTML elements that they do not support.
function supportsAudio()
{
var o = document.createElement( 'audio' );
return ( o.canPlayType );
}
In addition, this example does not require the webpage to contain an audio element before support for the audio element can be determined.
In cases where the DOM does not contain a built-in DOM object or property for the feature you want to detect, use createElement or a similar method to create a temporary object and then verify that the temporary object is the type of object you are interested in.
Note For best results, avoid assumptions about browsers between devices. A desktop browser may support different features than a mobile version of the same browser, which itself may differ from the same browser on a different device. With feature detection, you test all browsers for the features you need.
Searching for DOM Features
The hasFeature method indicates support for individual specifications or sets of features defined by a specification. For example, the following code sample shows how to determine whether a browser supports SVG.
bResult = document.implementation.hasFeature("org.w3c.svg", "1.0")
Specific feature strings are defined by the specification that defines the feature. For example, the World Wide Web Consortium (W3C) Document Object Model (DOM) Level 2 specification supports a variety of feature strings that correspond to modules of the overall specification.
Keep in mind that while the hasFeature method may indicate that a given browser supports a specific feature, it is entirely possible that the browser does not fully support every aspect of the feature. When in doubt, take time to research the implementation of a given feature in popular browsers.
For more information about the status of a given specification or set of features, refer to the resources provided by the organization that sponsors the specification. For example, the World Wide Web Consortium (W3C) website provides a Participation section that outlines resources regarding the development of W3C standards, including public discussion areas, status updates, and related information.
There are many ways to detect features supported by a web browser. Be sure to use detection techniques that relate directly to the feature that you want to use. For example, it is not appropriate to evaluate the color of a textbox in order to determine the font used to display its content. Associations like this tend to lead to difficulties down the road.