Treehouse

Detect Vendor Prefix with JavaScript

By on  

Regardless of our position on vendor prefixes, we have to live with them and occasionally use them to make things work.  These prefixes can be used in two formats:  the CSS format ("-moz-", as in -moz-element) and the JS format ("navigator.mozApps").  The awesome X-Tag project has a clever bit of JavaScript magic that detects those prefixes in the browser environment -- let me show you how it works!

The JavaScript

The first step is retrieving the HTML element's CSSStyleDeclaration:

var styles = window.getComputedStyle(document.documentElement, ''),

The next step is converting it to an Array object and searching for a known prefix type, settling on Opera if none is found:

pre = (Array.prototype.slice
      .call(styles)
      .join('') 
      .match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o'])
    )[1]

With the CSS prefix found, getting the JS-formatted prefix is simple:

return {
    dom: dom,
    lowercase: pre,
    css: '-' + pre + '-',
    js: pre[0].toUpperCase() + pre.substr(1)
  }

The returned object provides an object that looks something like:

Object {dom: "WebKit", lowercase: "webkit", css: "-webkit-", js: "Webkit"}

A complete representation of the vendor prefixing for the host browser. Here's the complete snippet:

var prefix = (function () {
  var styles = window.getComputedStyle(document.documentElement, ''),
    pre = (Array.prototype.slice
      .call(styles)
      .join('') 
      .match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o'])
    )[1],
    dom = ('WebKit|Moz|MS|O').match(new RegExp('(' + pre + ')', 'i'))[1];
  return {
    dom: dom,
    lowercase: pre,
    css: '-' + pre + '-',
    js: pre[0].toUpperCase() + pre.substr(1)
  };
})();

Grabbing the CSSStyleDeclaration from the HTML element is a clever move.  This method plays off of the fact that there will always be a vendor-prefixed property in the style declaration, which some may dislike, but is going to be effective for a long time to come.  What do you think of this method of vendor prefix detection?  Share your thoughts!

ydkjs-2.png

Recent Features

  • Chris Coyier’s Favorite CodePen Demos

    David asked me if I'd be up for a guest post picking out some of my favorite Pens from CodePen. A daunting task! There are so many! I managed to pick a few though that have blown me away over the past few months. If you...

  • CSS Gradients

    With CSS border-radius, I showed you how CSS can bridge the gap between design and development by adding rounded corners to elements.  CSS gradients are another step in that direction.  Now that CSS gradients are supported in Internet Explorer 8+, Firefox, Safari, and Chrome,...

Incredible Demos

  • Facebook Open Graph META Tags

    It's no secret that Facebook has become a major traffic driver for all types of websites.  Nowadays even large corporations steer consumers toward their Facebook pages instead of the corporate websites directly.  And of course there are Facebook "Like" and "Recommend" widgets on every website.  One...

  • CSS Kwicks

    One of the effects that made me excited about client side and JavaScript was the Kwicks effect.  Take a list of items and react to them accordingly when hovered.  Simple, sweet.  The effect was originally created with JavaScript but come five years later, our...

Discussion

  1. Maybe I’m not getting this right, but as far as I’ve played with manipulating properties (that still need a prefix) via JavaScript, I was under the impression that:
    – both webkit and Webkit work for WebKit browsers;
    ms works in IE, but not Ms;
    Moz and O work, but not moz or o (Opera also recognizes Webkit, but not webkit)

    link to test pen“>http://codepen.io/thebabydino/pen/HKstJ” rel=”nofollow”>link to test pen

    link to test pen" data-user="thebabydino">
  2. Hi David,

    Is there a way i can find is a css property value is supported by a particular browser. For ex: Android browser < 2.3 does not support overflow:auto or scroll. Is there a way i can find if the value is supported by a browser? i tried with javascript all i could find using document.body.style.propName but not for the property:value support.

    Thanks

    • That’s particularly what should do @support in the future.

      You could dig into Modernizr.testStyle and check the element property. Like with:

      Modernizr.testStyle('#dummy{ overflow: visible; overflow: scroll }', function(element){
        Modernizr.addTest('overflow', element.style.overflow === 'scroll');
      });

      If the browser can’t support scroll, I guess it will stick on visible.

    • I’m not sure, but i think you can create element, then apply desired value to property you want and check if it’s there :)
      Like this:

      function check (property, value) {
      
          var shadow = document.createElement('div');
      
          // Checking if we can even use this property
          if (!(property in shadow.style)) {
              return false;
          }
      
          // Applying desired value
          shadow.style[property] = value;
      
         // If the property is equal to the desired -> you can use it
          if (shadow.style[property] == value) {
              return true;
          }
      
          // Sadly, you can't use it :(
          return false;
      }
      
      check('display', 'none') // TRUE;
      check('display', 'hidden') // FALSE
      
    • Deepak

      Thanks everyone :)

  3. Aicke Schulz

    @David Walsh: Well done!

    @Deepak David: If you are looking for a generic approach look at Modernizr.

    p.s.: I know it is not the problem of this solution, I just want to mention, that in older browser versions the css style name can differ from the official / non-prefixed style name, e.g. -moz-border-radius-topright vs. border-top-right-radius. But this problem will fade away with time.

  4. It sure is an interesting way to detect style-prefixes.
    But why not detect it depending on userAgent string? Webkit -> webkit, Opera -> O, Mozilla -> Moz and IE -> ms ?
    And why store so many useful information u will never need like {dom: "WebKit", lowercase: "webkit", css: "-webkit-", js: "Webkit"}?

    I see the only reason to know a proper vendor prefix is for function that applies styles to the elements.
    Something like this:

    applyCSS = (function () {
    
        var prefix = getVendorPrefix(); // function, that detects prefix using UA string
    
        return function (element, styleName, styleValue) {
    
            // Collecting right style name
            var realStyleName = getStyleName();
    
            // Do nothing if there's no right style name
            if (!realStyleName) return;
    
            // Applying style
            element.style[realStyleName] = styleValue;
    
            /**
             * This function returns proper style name if there is one
             */
            function getStyleName () {
    
                var prefixedStyleName;
    
                // If there's no need for prefix
                if (styleName in element.style) {
                    return styleName;
                }
    
                // Creating style with vendor prefix
                prefixedStyleName = prefix + styleName.slice(0,1).toUpperCase() + styleName.slice(1);
    
                // Checking again
                if (prefixedStyleName in element.style) {
                    return prefixedStyleName;
                }
    
                // Browser has no support for this style. Shame! :)
                return false;
            }
            
        };
    
    
        /**
         * Returns proper vendor prefix name
         */
        function getVendorPrefix () {
        
            var ua = navigator.userAgent.toLowerCase(),
                match = /opera/.exec(ua) || /msie/.exec(ua) || /firefox/.exec(ua) || /(chrome|safari)/.exec(ua),
                vendors = {
                    opera: 'O',
                    chrome: 'webkit',
                    safari: 'webkit',
                    firefox: 'Moz',
                    msie: 'ms'
                };
            
            return vendors[match[0]];
        }
    
    })();
    

    Sorry My English.

  5. String indexing (e.g. pre[0]) is not supported by IE < 8. (As a side note, IE8 itself supports it for string literals only, not for string objects.)

    Also, substr is a non-standard String method. Why not just use slice or substring instead?

    • IE8 and down isn’t supported by x-tag, so that isn’t in the realm of desire. As for substr, I’ll pass that on.

  6. Internet Explorer supports prefix-less properties. So you should probably something more like this is appropriate in the modern world.

    MBP.getVendorPropertyName = function (prop) {
    
        var prefixes = ['Moz', 'Webkit', 'O', 'ms'],
                    vendorProp, i,
                    div = document.createElement('div'),
                    prop_ = prop.charAt(0).toUpperCase() + prop.substr(1);
    
        if (prop in div.style) {
            return prop;
        }
    
        for (i = 0; i < prefixes.length; ++i) {
    
            vendorProp = prefixes[i] + prop_;
    
            if (vendorProp in div.style) {
                return vendorProp;
            }
    
        }
    
        // Avoid memory leak in IE.
        this.div = null;
    };
    
  7. Hi David, thanks very much for sharing your efforts in making a JS function that tries to detect the vendor. I will try to use our code in the WP framework we’re working on. We generate CSS on client side (kind of like LESS does, but more simplistic) and I was looking for ways to minimize the CSS generated by our JS (in case you’re interested, see https://github.com/nexusthemes/nexusframework). In our framework the JS produces 779 CSS selectors. Injecting the CSS takes around 60 msecs on desktop (which is fair), but about 330 msecs on my ipad 1 in Chrome. I don’t know if lowering the amount of CSS will also lower the time it takes to process the CSs, but anything I can do to lower the 330 msecs sounds like worth investing :) Using your function I hope to reduce 6 lines of CSS;

    // helper function to get the css output for a lineair gradient between the 2 specified colors
    function nxs_js_getlineairgradientcss(colora, colorb)
    {
    	var result = "";
    	result += "background-color: " + colora + ";";
    	result += "background: -o-linear-gradient(" + colorb + ", " + colora + ");";
    	result += "background: -moz-linear-gradient(" + colorb + ", " + colora + ");";
    	result += "background: -webkit-linear-gradient(" + colorb + ", " + colora + ");";
    	result += "background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(" + colorb + "), to(" + colora + "));";
    	result += "filter: progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr=" + colorb + ",EndColorStr=" + colora + ");";
    	return result;
    }
    

    Into just 1 or 2 lines.

    Anyways, thanks again.

  8. I’ve been using the snippet below in some of my jQuery plugins. How does it compare to your method?

    var prefix = (/mozilla/.test(navigator.userAgent.toLowerCase()) && 
                 !/webkit/.test(navigator.userAgent.toLowerCase())) ? '-moz-' : 
                 (/webkit/.test(navigator.userAgent.toLowerCase())) ? '-webkit-' :
                 (/msie/.test(navigator.userAgent.toLowerCase()))   ? '-ms-' :
                 (/opera/.test(navigator.userAgent.toLowerCase()))  ? '-o-' : '';

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!