Quick Tip: Detect CSS3 Support in Browsers with JavaScript
videos

Quick Tip: Detect CSS3 Support in Browsers with JavaScript

Tutorial Details
  • Topic: Feature Detection
  • Difficulty: Moderate
  • Format: Video
  • Length: 17 Minutes
This entry is part 9 of 16 in the CSS3 Mastery Session
« PreviousNext »

Isn’t it fun that we get to play with the latest CSS techniques, like shadows and transitions? There’s only one problem: how do we compensate, or more importantly, detect the browsers that do not support them? Well, there’s a few solutions. In this tutorial and screencast, though, we’ll create a JavaScript function that will accept a CSS property name as its parameter, and will return a boolean, indicating whether or not the browser supports the passed property.


Prefer a Video Tutorial?

Press the HD button for a clearer picture.

Subscribe to our YouTube page to watch all of the video tutorials!


Step 1

Let’s begin by determining how we want to call our function. We’ll keep things simple here; something like the following should do the trick:

if ( supports('textShadow') ) {
   document.documentElement.className += ' textShadow';
}

That should be the final function call. When we pass a CSS property name to the supports() function, it’ll return a boolean. If true, we’ll attach a className to the documentElement, or <html>. This will then provide us with a new `class` name to hook onto, from our stylesheet.


Step 2

Next, we’ll construct the supports() function.

var supports = (function() {

})();

Why aren’t we making supports equal to a standard function? The answer is because we have a bit of prep work to do first, and there’s absolutely no reason to repeat those tasks over and over every single time the function is called. In cases like this, it’s best to make supports equal to whatever is returned from the self-executing function.


Step 3

To test whether or not the browser supports specific properties, we need to create a *dummy* element, for testing. This generated element will never actually be inserted into the DOM; think of it as a test dummy!

var div = document.createElement('div');

As you’re probably aware of, there are a handful of vendor-prefixes that we can use, when working with CSS3 properties:

  • -moz
  • -webkit
  • -o
  • -ms
  • -khtml

Our JavaScript will need to filter through those prefixes, and test them. So, let’s place them in an array; we’ll call it, vendors.

var div = document.createElement('div'),
    vendors = 'Khtml Ms O Moz Webkit'.split(' ');

Using the split() function to create an array from a string is admittedly lazy, but it saves a handful of seconds!

As we’ll be filtering through this array, let’s be good boys and girls, and cache the length of the array as well.

var div = document.createElement('div'),
  vendors = 'Khtml Ms O Moz Webkit'.split(' '),
  len = vendors.length;

The prep work, above, is static, in nature, and doesn’t need to be repeated every time we call supports(). This is why we only run it once, when the page loads. Now, let’s return the function that will actually be assigned to the supports variable.

return function(prop) {

};

The beauty of closures is that, even though supports() is equal to that returned function, it still has access to the div, vendors, and len variables.


Step 4

The immediate test: if the passed property is available to the div‘s style attribute, we know the browser supports the property; so return true.

return function(prop) {
   if ( prop in div.style ) return true;
};

Think of, say, the text-shadow CSS3 property. Most modern browsers support it, without the need for a vendor prefix. With that in mind, why filter through all of the prefixes if we don’t need to? That’s why we place this check at the top.


Step 5

You’re likely used to typing CSS3 property names, like so: -moz-box-shadow. However, if, in Firebug, you review the style object, you’ll find that it’s spelled, MozBoxShadow. As such, if we test:

'mozboxShadow' in div.style // false

False will be returned. This value is case-sensitive.

Case Sensitive

This means that, if the user passes boxShadow to the supports() function, it’ll fail. Let’s think ahead, and check if the first letter of the argument is lowercase. If it is, we’ll fix the error for them.

return function(prop) {
   if ( prop in div.style ) return true;

   prop = prop.replace(/^[a-z]/, function(val) {
      return val.toUpperCase();
   });

};

Regular expressions to the rescue! Above, we’re checking if there is a single lowercase letter at the beginning of the string (^). Only on the condition that one is found, we use the toUpperCase() function to capitalize the letter.


Step 6

We next need to filter through the vendors array, and test if there’s a match. For instance, if box-shadow is passed, we should test if the style attribute of the div contains any of the following:

  • MozBoxShadow
  • WebkitBoxShadow
  • MsBoxShadow
  • OBoxShadow
  • KhtmlBoxShadow

If a match is found, we can return true, because the browser does, indeed, provide support for box shadows!

return function(prop) {
   if ( prop in div.style ) return true;

   prop = prop.replace(/^[a-z]/, function(val) {
      return val.toUpperCase();
   });

   while(len--) {
      if ( vendors[len] + prop in div.style ) {
            return true;       
      } 
   }  
};

Though we could use a for statement to filter through the array, there’s no real need to in this case.

  • The order isn’t important
  • while statements are quicker to type, and require fewer characters
  • There’s a tiny performance improvement

Don’t be confused by vendors[len] + prop; simply replace those names with their real-life values: MozBoxShadow.


Step 7

But, what if none of those values match? In that case, the browser doesn’t seem to support the property, in which case we should return false.

while(len--) {
   if ( vendors[len] + prop in div.style ) {
         return true;       
   } 
} 
return false;

That should do it for our function! Let’s test it out, by applying a className to the html element, if the browser supports, say, the text-stroke property (which only webkit does).

if ( supports('textStroke') ) {
   document.documentElement.className += ' textStroke';
}

Step 8: Usage

With a class name that we can now hook onto, let’s try it out in our stylesheet.

/* fallback */
h1 {
   color: black;
}   

/* text-stroke support */
.textStroke h1 {
  color: white;
  -webkit-text-stroke: 2px black;
}

Final Source Code

var supports = (function() {
   var div = document.createElement('div'),
      vendors = 'Khtml Ms O Moz Webkit'.split(' '),
      len = vendors.length;

   return function(prop) {
      if ( prop in div.style ) return true;

      prop = prop.replace(/^[a-z]/, function(val) {
         return val.toUpperCase();
      });

      while(len--) {
         if ( vendors[len] + prop in div.style ) {
            // browser supports box-shadow. Do what you need.
            // Or use a bang (!) to test if the browser doesn't.
            return true;
         } 
      }
      return false;
   };
})();

if ( supports('textShadow') ) {
   document.documentElement.className += ' textShadow';
}

For a more comprehensive solution, refer to the Modernizr library.

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://miketaylr.com Mike Taylor

    Microsoft implements their prefix as `ms`, rather than Ms. So you should update your vendors variable to reflect it. :)

    See: https://github.com/Modernizr/Modernizr/blob/master/modernizr.js#L88

    • http://www.facebook.com/profile.php?id=100003405391950 Bianca

      Asa da! In ssfarit un tutorial zdravan luat integral de pe alt site. Cred ca o sa intru des pe blogul asta ca sa vad tutoriale de pe alte site-uri…nu?

      • Mark

        muie fa!!!

  • http://www.chimply.net Dieter Bogaert

    I’m sorry but I don’t really see the point of ever needing this, it’s nice to know that it’s possible to detect these things but that’s about it…
    what’s really the difference between this and using an if IE stylesheet? Seems to me that this is even more effort.

    Or am I missing the point somewhere in this article?

    PS: I’m always impressed by the way you code, Jeffrey. You make me feel so inexperienced sometimes. Is nettuts your fulltime profession or do you work at google or something? Where did you learn how to code like that? The javascript I saw in school was so basic compared to this… I’m already looking forward to your next article ;-)

    • Michael Butler

      Simple, if the browser does not support a CSS property like transition, 3D transforms, or animation keyframes, you can fallback to doing it with JavaScript.

      • Gege

        Very useful detection. Thumbs up!

  • http://laroouse.com piyansitll

    harika bir çalışma olmuş sağolun

  • Remoun

    Have you tried running the function more than once (with checking for vendor extensions)? I think you’d be surprised.

    The len variable would be decremented to -1 when the function is first run. Since it’s a closure, the next call to supports() would loop from -1 down to zero, which would take quite a while (not infinite, due to integer underflow).

    I suggest you declare the len variable inside the returned function, as that’s the only place it’s used.

    • Jason

      That’s true. I also noticed it.

    • Bill Fisher

      Thanks for pointing out to problem of running this multiple times with len in the closure. The function was crashing the browser, but now with your tip, it’s not.

    • Michael Butler

      Noticed this too. It’s a near infinite loop.

  • Ash

    There is a jquery plugin version of this for those interested:

    https://gist.github.com/556448

    via

    http://api.jquery.com/jQuery.support/

  • http://www.maiconweb.com Maicon Sobczak

    I learned some new ways to write the code. Very interesting tut.

  • daniel

    very interesting! thanks

    thanks for the good points made in the comments as well
    I have to learn some more about closures for sure ;)

  • david

    It’s possible to add a css file for example if you detect that the browser accepts textStroke so in that way you don’t have the property in your style.css/ you just add it on webkit.css in that way that’s going to be fire up once that the script detects it?

  • http://carloshermoso.com Carlos Hermoso

    I really enjoyed this post, very interesting. Thanks!

  • Dave

    A better lesson in Javascript closures than it is in actual functionality.

  • yanghui

    Now,not all browsers support CSS3.It’s a pity.

  • http://www.jeffadams.co.uk Jeff Adams

    Must admit I didn’t pay too much attention to this when it came out but I’ve just found a need fo rthis – so thanks for this it really helped me out :p

  • FredChin

    String.prototype.toCSSCase=function(){
    return this.replace(/-([a-z])/g,function($,i){
    return i.toUpperCase();
    });
    };
    var supportCSS = (function(){
    var prefixs = {msie:”-ms-”,mozilla:”-moz-”,webkit:”-webkit-”,opera:”-o-”,khtml:”-khtml-”};
    var prefix = “”;
    (function(){
    //set prefix, implemented with jQuery
    $.browser.khtml = $.browser.khtml || (navigator.userAgent.indexOf(“Konqueror”) != -1);
    for(var i in prefixs){
    if($.browser[i]){
    prefix = prefixs[i];
    }
    }
    })();
    return function (prop){
    return prop.toCSSCase() in document.documentElement.style ||// support
    (prefix+prop).toCSSCase() in document.documentElement.style;// implementation
    };
    })();

  • FredChin

    Sorry, there is bug above.
    One solution is to remove the first dash in -ms- vender prefix, just like this:

    part of the original code:
    var prefixs = {msie:”-ms-”,mozilla:”-moz-”,webkit:”-webkit-”,opera:”-o-”,khtml:”-khtml-”};

    code after fixed:
    var prefixs = {msie:”ms-”,mozilla:”-moz-”,webkit:”-webkit-”,opera:”-o-”,khtml:”-khtml-”};

    Fuck IE.

  • Louis

    It’s a little cheaper and clearer to change:

    var div = document.createElement(‘div’)

    to:

    var style = document.createElement(‘div’).style

    and replace:

    …prop in div.style…

    with:

    …prop in style…

  • http://www.caiobianchi.com Caio Bianchi

    Too bad we’re all too spoiled on Modernizr already, but this little tip might come in handy if you’re writing a shim for a specific css3 property, it’s lightweight and runs natively.

    PS.: the “if in” syntax won’t work in oldIEs, as it was introduced in JS 1.6. Instead you can use “indexOf”, if you need to support older browsers.

  • Liam

    I enjoy that you go back to fix the css of things that don’t look aesthetically pleasing to you.

    Great stuff!

  • Vijay Nair

    This script does not work correctly in Chrome when you call the method for more than 1 time. Consider replacing the “while loop” with “for loop” or re-instantiate the array length with-in the function return.