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!

Recent Features

  • By
    9 More Mind-Blowing WebGL Demos

    With Firefox OS, asm.js, and the push for browser performance improvements, canvas and WebGL technologies are opening a world of possibilities.  I featured 9 Mind-Blowing Canvas Demos and then took it up a level with 9 Mind-Blowing WebGL Demos, but I want to outdo...

  • By
    fetch API

    One of the worst kept secrets about AJAX on the web is that the underlying API for it, XMLHttpRequest, wasn't really made for what we've been using it for.  We've done well to create elegant APIs around XHR but we know we can do better.  Our effort to...

Incredible Demos

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-' : '';
  9. Nice article.

    How would one not use the the prefix, say, if the browser is able to run something prefix free?

    For example, an up-to-date Chrome doesn’t need -webkit- for transform.

    thanks

  10. Nick OConnor

    Sorry, please delete that. Despite cssText apparently being supported by all browsers, Firefox returns an empty string :-(

  11. dom = ('WebKit|Moz|MS|O').match(new RegExp('(' + pre + ')', 'i'))[1];

    The ‘O’ should appear before ‘Moz’, else on opera it would match the lowercase ‘o’ in ‘Moz’.

    dom = ('WebKit|O|Moz|MS').match(new RegExp('(' + pre + ')', 'i'))[1];

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