This gives information about making code which differs for different browsers, using client-side browser sniffing and other techniques.
Sometimes, to overcome browser differences, it is necessary to use different code for different browsers, although it is less necessary now than it was years ago when fundamental browser differences were greater. Different code should be used as seldom as possible: ideally never.
There are several ways to do this, with different levels of reliability, for different purposes:
Examples are given below.
This technique enables different HTML to be parsed by IE, by different versions of IE, or by all browsers other than IE. With this technique HTML can be enclosed in a special form of comments in such a way that browsers will view the HTML as comments instead of HTML unless the conditions specified in the comments are just right.
Microsoft introduced conditional comments with IE5 for Windows. They are supported by all versions of IE for Windows, from version 5 up, which is all versions of IE in common use today. IE 10 may not support conditional comments, however.
The big advantage of conditional comments is that they are completely reliable, which is not something which can be said of other techniques.
The big disadvantages are that (a) they are HTML only, so can’t be put in CSS or JavaScript files, and (b) they hide the conditional HTML, so validators can’t see the HTML, allowing hidden errors to lurk. There are several simple and obvious ways to overcome the first problem, e.g. putting the code specific to certain browsers in separate CSS or JavaScript files, and using conditional comments to link to the appropriate files. The best way to overcome the second problem is to keep any HTML within conditional comments both short and simple.
One use of conditional comments, as noted in the sidebar, is to link to stylesheets which only certain versions of IE will see. For example, the following conditional comment appears in this page:
<!--[if lt IE 6]>
<link rel="stylesheet" type="text/css" href="style_ie5x.css">
<![endif]-->
This links to a special stylesheet for IE less than IE 6. The conditional comments follow HTML (not shown above) which links to a standard stylesheet, so the IE stylesheet simply augments the standard stylesheet, typically to overcome IE defects, or to hide IE-specific CSS such as filters and scrollbar styling. One benefit of putting IE-specific CSS in IE-specific stylesheets is that this CSS will be hidden from CSS validators, which avoids messy validator error messages.
Microsoft documents conditional comments completely. One detail which is commonly overlooked is that, to specify IE 5.5, it must be referred to as version 5.5000.
This technique uses browser differences in JavaScript objects to do things differently in different browsers.
The big advantage of object detection is that it does not require that the browser be identified, which dispenses with problems there might be with faulty browser identification.
The big disadvantage is that one must take great care in examining objects: an object must not be used unless it exists, but the fact that it exists does not always means that it can be used. For example:
Unlike all other browsers, IE does not use navigator.plugins[] to keep information about plugins: IE keeps such information
elsewhere; IE does offer the array, but it is always empty. Checking to see whether the array exists, therefore, means nothing: the array always exists.
Checking the
contents of the array is somewhat more useful: if it is not empty, the browser is not IE, and the array may be used; however, if it is empty,
this could mean either that the browser is IE, or that there are no plugins.
The document.all[] array is used by old versions of IE to get tag elements by ID, instead of the standard
method document.getElementById(), but it is important to do object detection in the right order: some browsers other than IE
support the standard method, but also have a document.all[] array devoid of tag elements; JavaScript should therefore
check for the method first, and only if it does not exist, check for the array. For example:
function getElement ( id )
{
var element = null;
if ( document.getElementById )
element = document.getElementById( id );
else if ( document.all )
element = document.all[id];
return( element );
}
Object detection is typically used either to overcome a browser defect, or to take advantage of a browser feature which is not universal.
An example of object detection to overcome a defect appears in the above function, getElement(), which overcomes
the failure of old versions of IE to support the standard document.getElementById().
An example of detection to take advantage of features which are not universal would be detection of canvas.getContext()
to determine whether the browser supports JavaScript related to the <canvas> tag, which is in the proposed HTML 5 standard, but not
in any official standard.
This technique uses browser CSS differences to make different browsers see different CSS.
This is complex, and quite well described elsewhere, for example in Eric Meyer’s Tricking Browsers and Hiding Styles, so will not be discussed here further. Note that he mentions some tricks which apply only to extinct browsers.
Caution : some tricks depend on bugs in how browsers process invalid CSS, or on how well browsers support CSS features, so the tricks may not work, or may not work exactly the same, when browsers are updated.
This technique uses CSS 3 media queries to load stylesheets for devices with smaller screens, e.g. for smartphones and tablet PCs. Typically the stylesheet will override the normal stylesheet to adjust the normal layout for small screens: if the site is designed in accordance with fluid design principles, the mobile stylesheet is often fairly small. This technique is simple and well supported by better smartphones.
For example this loads stylesheet style_mob.css only if the screen width is 480px or less:
<link type="text/css" href="style_mob.css" rel="stylesheet" media="only screen and (max-width:480px)">
For details see the pertinent CSS 3 specification.
This technique involves JavaScript code — commonly called a browser sniffer — which identifies the browser by
examining certain JavaScript objects, chiefly the navigator.userAgent string.
This is the least reliable approach to dealing with browser differences, but it is more reliable than many give it credit for, and it often
works well when other techniques cannot be applied. Browser sniffers have a sordid reputation, largely because of the widespread use of sniffers
which are far too simplistic. The earliest sniffers dealt only with versions of IE and Netscape which today are extinct, e.g. IE 3-4 and
Netscape 3-4. The early sniffers could not handle the range of browsers which appeared later, and in many cases the sniffers were never updated, so many sites
broke with newer browsers. This encouraged browser makers to set userAgent strings which mimiced those of more common
browsers, to make broken sites think the browser was one they expected: but this made more subtle sniffers necessary for correct
identification. Also, many sniffers updated to handle newer browsers are very naïve: e.g. they assume that, if
'MSIE' appears in the user agent string, the browser is Internet Explorer; this overlooks the fact that user agent strings
of other browsers, such as Opera, may contain 'MSIE'. Much better sniffers are possible.
The big disadvantage to browser sniffing, of course, is that it requires that JavaScript be enabled.
Another disadvantage is that it relies on the value
of the navigator.userAgent string, and this string can be faked: e.g., someone using Firefox could use a plugin which gives it the
same user agent string as IE. This disadvantage is not, however, as serious as one might think, because someone would use such a plugin only for a
site which has a sniffer so bad that the site cannot work with the right user agent. No one would need to fake the user agent for a site with a good
sniffer.
This describes a browser sniffer I designed which I think is very good. It has four parts:
isTrulyIE only if the browser is IE.The HTML goes in the <head> section, and creates a variable isTrulyIE only if the browser is IE 5 (or later) for Windows:
<!--[if gte IE 5]>
<script type="text/javascript">
var isTrulyIE = true;
</script>
<![endif]-->
This JavaScript constructs an object with information about the browser. It takes two optional arguments:
a string to be used as the userAgent string, with the real navigator.userAgent as the default;
and a string to be used as the appVersion string, with the real navigator.appVersion as the default.
function cBrowser ( ua, ver )
{
if ( arguments.length == 0 )
ua = navigator.userAgent;
this.ua = ua.toLowerCase();
if ( arguments.length < 2 )
ver = navigator.appVersion;
this.isIE8 = this.isIE9 = this.isIE10 = false;
if ( typeof(isTrulyIE) != 'undefined' )
{
this.isIE = true;
this.isKhtml = this.isChrome = this.isSafari = this.isOpera = this.isGecko = this.isNetscape = this.isWebtv = false;
}
else
{
this.isKhtml = (this.ua.indexOf("khtml") != -1);
this.isChrome = (this.ua.indexOf("chrome/") != -1);
this.isSafari = (this.ua.indexOf("safari") != -1);
this.isOpera = (this.ua.indexOf("opera") != -1);
this.isGecko = !this.isKhtml && !this.isChrome && !this.isSafari && !this.isOpera &&
(this.ua.indexOf("gecko/") != -1);
this.isNetscape = !this.isKhtml && !this.isChrome && !this.isSafari && !this.isOpera &&
( (this.ua.indexOf('mozilla')!=-1) &&
((this.ua.indexOf('spoofer')==-1) &&
(this.ua.indexOf('compatible')==-1)) );
this.isWebtv = (this.ua.indexOf("webtv")!=-1);
this.isIE = !this.isWebtv && !this.isOpera && (this.ua.indexOf("msie") != -1);
this.version = ver;
if ( this.isNetscape && (this.version>=5) )
this.isGecko = true;
}
if ( this.isIE )
{
this.version = parseFloat(this.ua.substring(4+this.ua.indexOf("msie")));
this.isIE8 = (this.version == "8.0") || (this.ua.indexOf("trident/4") != -1);
this.isIE9 = (this.version == "9.0") || (this.ua.indexOf("trident/5") != -1);
this.isIE10 = (this.version == "10.0") || (this.ua.indexOf("trident/6") != -1);
this.version = new cVersion( this.version );
}
else if ( this.isOpera )
{
if ( ua.indexOf('version/') != -1 )
this.verBrowser = new cVersion( ua.substring(8+ua.indexOf('version/')) );
else if ( ua.indexOf('opera/') == 0 )
this.version = parseFloat(this.ua.substring(1+this.ua.indexOf("/")));
else if ( this.ua.indexOf("opera/") != -1 )
this.version = parseFloat(this.ua.substring(2+this.ua.indexOf(")")));
else
this.version = parseFloat(this.ua.substring(6+this.ua.indexOf("opera")));
}
}
The above creates an object which has the following properties:
navigator.userAgentThis JavaScript constructs an object which holds a version vector, with methods which act on the vector.
The methods are cVersion.toString(), which returns a string corresponding to the version vector in a
cVersion object, and cVersion.comp() which compares two version vectors and returns a value indicating
whether the first is less than, equal to, or greater than the second version vector.
function cVersion ( version, separator, bSkipSpace )
{
if ( arguments.length < 1 )
version = '0';
if ( arguments.length < 2 )
separator = '.';
if ( arguments.length < 3 )
bSkipSpace = false;
if ( version instanceof cVersion )
{
this.separator = (arguments.length < 2) ? version.separator : separator;
this.v = new Array();
this.v = version.v;
}
else
{
var s = ( typeof(version) == 'number' ) ? version.toString() : version;
this.separator = separator;
this.v = new Array();
var vindex = 0;
var sindex = 0;
var c;
this.v[vindex] = '';
if ( bSkipSpace) for ( ; sindex < s.length; ++sindex )
{
c = s.charAt(sindex);
if ( c != ' ' )
break;
}
for ( ; sindex < s.length; ++sindex )
{
c = s.charAt(sindex);
if ( c == separator )
this.v[++vindex] = '';
else if ( (c >= '0') && (c <= '9') )
this.v[vindex] += c.toString();
else
break;
}
}
return;
}
cVersion.prototype.toString = function ( separator )
{
if ( arguments.length < 1 )
separator = this.separator;
var rv = '';
for ( var i = 0; i < this.v.length; ++i )
{
if ( i == 0 )
rv += this.v[0];
else
rv += separator + this.v[i];
}
return rv;
}
cVersion.prototype.comp = function ( version2 )
{
var operand;
if ( arguments.length < 1 )
version2 = '0';
if ( version2 instanceof cVersion )
operand = version2;
else
operand = new cVersion( version2 );
var nLoops = Math.max( this.v.length, operand.v.length );
var rv = 0;
for ( var i = 0; i < nLoops; ++i )
{
var n1 = Number( (i < this.v.length) ? this.v[i] : 0 );
var n2 = Number( (i < operand.v.length) ? operand.v[i] : 0 );
if ( n1 == n2 )
continue;
else if ( n1 < n2 )
{ rv = -1; break; }
else
{ rv = 1; break; }
}
return rv;
}
An object may now be created to hold the browser information, e.g.:
var browser = new cBrowser();
If JavaScript is enabled the above code reliably detects IE with 100% certainty due to the dependence on conditional comments, for IE 5 (and up) for Windows, even if the user agent has been faked.
Identification of other browsers will be extremely reliable: not 100%, but close. This is partly because the certain detection of
IE eliminates a lot of possible errors, but also because the code is very careful in analyzing the user agent: for example, before checking to
see if the user agent contains 'gecko/', it excludes browsers whose user agents may mimic Gecko user agents, e.g. Opera.
The code is likely to misidentify the browser only if the browser is not IE and the entire user agent string has been faked: and, as pointed out
above, the user would have no need to fake the user agent string for a site with a good browser sniffer; in practical terms, therefore, this code
is exceedingly reliable.
The least reliable information is the version number: it should be correct for IE or Opera.
More reliable identification of the version is possible: I have code to do it, but is not listed here because it rarely matters, since few people use
very old versions of browsers other than IE. Note : for IE 8 and IE 9 the version number may be wrongly identified because
IE 8 and IE9 may emulate an older version of IE; for example, if IE 8 decides to render a page in IE 7 mode, the version number will be 7, not 8; but in this
case the version number, though wrong technically, is right practically.
Note : reliable identification of the version of Safai can be very hard,
because the navigator.userAgent string for older versions of Safari does not contain the version number, but instead contains build numbers which can only with effort be mapped to
the version numbers.
This browser sniffer detects a wider range of browsers than many sites will need to recognize, e.g. MSN-TV (WebTV). Moreover, this sniffer retains a complete version vector rather than a major version number, e.g. 4.1.1 instead of just 4. This is intentional: a key concept of the above code is that it is a standard block of code which can be plugged into any site, and which can later be safely replaced by an updated equivalent. The overhead of detecting things unneeded for some sites is a small price for having a single reliable, reüsable module.
Here are some examples of achieving different code for different browsers:
One way to specify different CSS rules for IE, or for different versions of IE, is to link to one master stylesheet file which defines the standard rules for all browsers, and then to use conditional comments to link to other, much smaller stylesheet files which set a few rules, or override a few rules, to deal with the vagaries of various versions of IE. For example:
<link rel="stylesheet" type="text/css" href="style.css" />
<!--[if lt IE 6]>
<link rel="stylesheet" type="text/css" href="style_ie5x.css">
<![endif]-->
<!--[if gte IE 5]>
<link rel="stylesheet" type="text/css" href="style_ie.css">
<![endif]-->
This links to the standard stylesheet, style.css. If the browser is IE 5.x for Windows, this then links to
style_ie5x.css. If the browser is any version of IE for Windows from 5.0 up,
this then links to style_ie.css.
This is completely reliable for IE for Windows 5 and up.
One way to cope with mobile devices is to link to a standard stylesheet used for all pages, and then to conditionally link to a stylesheet which copes with differences in mobile devices. For example:
<link type="text/css" href="mobile.css" rel="stylesheet"
media="only screen and (max-width:480px)">
This links to mobile.css if the browser supports CSS 3 media queries and the device has a screen width of 480px or less.
This is reliable for modern mobile browsers which use a Gecko, Opera, or WebKit browser engine.
Sometimes JavaScript is needed which is different for a specific version of IE. For example, I have several sites with JavaScript that
generates HTML to produce CSS-styled bar charts, but it does not work well for IE 5.0x because its poor CSS support produces ugly results.
What I did for these sites is use conditional comments to set a JavaScript variable calBarCharts, i.e:
<!--[if lt IE 5.5000]>
<script type="text/javascript">
var calBarCharts = false;
</script>
<![endif]-->
In some cases the bar charts were not critical and could be omitted, so the JavaScript generating the HTML would generate nothing if the variable existed and was false.
In some cases the bar charts were more important, so the JavaScript generating the HTML would generate different HTML, producing less attractive but adequate bar charts, if the variable existed and was false.
In either case, using conditional comments for this purpose eliminates the need for a complex browser sniffer. This is completely reliable for IE for Windows 5 and up.
Sometimes JavaScript is needed which can scroll to a bookmark on the current page. Some browsers, however, do not support the preferred method, so an alternate method is used for these browsers. In the following function, object detection is used to decide which method to use:
function myScrollToAnchor ( id )
{
if ( arguments.length < 1 )
id = window.location.hash;
if ( id.indexOf('#') == 0 )
id = id.substring(1);
var o = getElement( id );
if ( o )
{
if ( (typeof(o.offsetTop) != 'undefined') && (typeof(window.scrollTo) != 'undefined') )
{
var x = 0;
var y = 0;
while ( o )
{
x += o.offsetLeft;
y += o.offsetTop;
o = o.offsetParent;
}
window.scrollTo( x, y );
}
else
window.location.replace( '#' + id );
}
}
The function takes an optional argument, the ID for the bookmark. If the argument is omitted, the bookmark is extracted from the URL for the current page.
Object detection is done in two locations, marked above by gold underlines:
getElement() function listed earlier is used to get the element associated with
the ID, and, as pointed out earlier, that function uses object detection to decide how to get the element.offsetTop property and the window has a scrollTo() method:
iff both exist — and not all browsers support both — the preferred method of scrolling, using window.scrollTo() is used; if either one does not exist, another
method, using window.location.replace() is used instead.The above should be reliable unless the browser supports neither method: I am not aware of any which don’t.
Caution : the above function has been observed to fail with certain old browsers if the function is called just after the
HTML containing the anchor has been dynamically changed, e.g. using innerHTML. It appears that the browser tries to scroll to the bookmark before the dynamic changes
have completed.
Consider the problem of extracting a filename from a pathname. The following function was created to do this, which uses a method akin to object detection to do it:
function myGetFilename ( pathname )
{
var sFilename;
if ( (arguments.length == 0) || (pathname == null) )
pathname = document.location.pathname;
var nSlash = pathname.lastIndexOf( '/' );
if ( nSlash == -1 )
sFilename = pathname;
else
sFilename = pathname.substring(nSlash+1);
nSlash = pathname.lastIndexOf( '\\' );
if ( nSlash != -1 )
sFilename = sFilename.substring(nSlash+1);
return( sFilename );
}
This function takes a pathname as an optional argument: if the argument is omitted or null, the pathname to the current document is used.
The basic algorithm looks for the last '/' in the pathname: if not found, the filename is the pathname; if found, the filename is
the string to the right of the '/'. However, some older versions of IE use a '\' instead of a '/' to
separate directory names and the filename in a pathname: so the
function then looks for the last '\' in the pathname: if not found, the filename is the pathname; if found, the filename is
the string to the right of the '\'. This enables the function to work no matter which browser is used: not exactly by object detection,
but, like object detection, the identity of the browser need not be determined, as it suffices simply to recognize the different behaviours of
different browsers.
When using CSS to style text, one common thing to do is set the basic font size. I often prefer to do so using a method which honours the user’s preferred font size. With CSS, one should be able to do this using:
html { font-size:medium; }
Unfortunately, this assumes that the default size is medium, which is what it should be,
but IE5 wrongly uses small.
This means that, to get consistent results, setting the size for IE5 requires:
html { font-size:small; }
One method to this with one rule uses a CSS trick which depends on how IE5 handles invalid CSS:
html { font-size:small; voice-family: "\"}\""; voice-family:inherit; font-size:medium; }
This works pretty well, but a better method is to put this in the standard stylesheet …
html { font-size:medium; }
… and then to put the CSS for IE5 in an IE5-specific stylesheet, using conditional comments, for example …
<!--[if lt IE 6]>
<link rel="stylesheet" type="text/css" href="style_ie5x.css">
<![endif]-->
… which has the merit of using an IE5-specific file into which other CSS can also be put to get around IE5’s many other defects.
Object detection can be used to see whether the browser supports various HTML 5 features. Here are JavaScript code fragments for detecting support
of the <audio>, <video>, and <canvas> tags:
if ( window.HTMLAudioElement )
{
// <audio> is supported
}
if ( window.HTMLVideoElement )
{
// <video> is supported
}
if ( window.HTMLCanvasElement )
{
// <canvas> is supported
}