A Best Practice for Programming with Vendor Prefixes

Vendor prefixes enable Web developers to experiment with new standards before they reach the Candidate Recommendation stage. I previously wrote how these prefixes are also a mechanism browser vendors use for handling timing conflicts between implementations and specifications. In building demos of new features for our IE Test Drive site and in various presentations, many of us on the IE team deal extensively with vendor prefixes.

This blog post describes a pattern we’ve used in some recent demos that’s making things significantly easier for us and has become a best practice. We’d like to share it with you and hear your thoughts on this approach or any others you consider a best practice.

Error-Prone Code

When using script to access CSS properties with vendor prefixes, it’s easy to end up with code that looks like this:

var elm = document.getElementById("myElement");

elm.style.msTransitionProperty = "all";

elm.style.msTransitionDuration = "3s";

elm.style.msTransitionDelay = "0s";

elm.style.webkitTransitionProperty = "all";

elm.style.webkitTransitionDuration = "3s";

elm.style.webkitTransitionDelay = "0s";

elm.style.MozTransitionProperty = "all";

elm.style.MozTransitionDuration = "3s";

elm.style.MozTransitionDelay = "0s";

elm.style.OTransitionProperty = "all";

elm.style.OTransitionDuration = "3s";

elm.style.OTransitionDelay = "0s";

While functional, it’s bloated, ugly, and error-prone.

Consolidating Vendor-Prefixed Properties to a Single Name

A better pattern is to define a method that loops through a list of property names and returns the first supported property or null if the browser doesn’t support any of them.

function FirstSupportedPropertyName(prefixedPropertyNames) {

var tempDiv = document.createElement("div");

for (var i = 0; i < prefixedPropertyNames.length; ++i) {

if (typeof tempDiv.style[prefixedPropertyNames[i]] != 'undefined')

return prefixedPropertyNames[i];

}

return null;

}

We then initialize a variable for each vendor-prefixed property we use, passing it an array of possible properties in the order we prefer to use them.

var transformName = FirstSupportedPropertyName(["transform", "msTransform", "MozTransform", "WebkitTransform", "OTransform"]);

var backfaceVisibilityName = FirstSupportedPropertyName(["backfaceVisibility", "msBackfaceVisibility", "MozBackfaceVisibility", "WebkitBackfaceVisibility", "OBackfaceVisibility"]);

var transitionName = FirstSupportedPropertyName(["transition", "msTransition", "MozTransition", "WebkitTransition", "OTransition"]);

var animationName = FirstSupportedPropertyName(["animation", "msAnimation", "MozAnimation", "WebkitAnimation", "OAnimation"]);

var gridName = FirstSupportedPropertyName(["gridRow", "msGridRow", "MozGridRow", "WebkitGridRow", "OGridRow"]);

var regionsName = FirstSupportedPropertyName(["flowFrom", "msFlowFrom", "MozFlowFrom", "WebkitFlowFrom", "OFlowFrom"]);

var hyphensName = FirstSupportedPropertyName(["hyphens", "msHyphens", "MozHyphens", "WebkitHyphens", "OHyphens"]);

var columnName = FirstSupportedPropertyName(["columnCount", "msColumnCount", "MozColumnCount", "WebkitColumnCount", "OColumnCount"]);

Then code throughout your site that uses these properties becomes something like this:

var elm = document.getElementById("myElement");

if (transitionName) {

elm.style[transitionName + "Property"] = "all";

elm.style[transitionName + "Duration"] = "3s";

elm.style[transitionName + "Delay"] = "0s";

}

else {

// fallback for browsers without CSS3 transitions

}

Note the simple feature detection enabled by returning null in FirstSupportedPropertyName.

That pattern also works when CSS properties have vendor prefixes. You can use a slightly different pattern for cases where a CSS value (for example, linear-gradient) has vendor prefixes:

function FirstSupportedFunctionName(property, prefixedFunctionNames, argString) {

var tempDiv = document.createElement("div");

for (var i = 0; i < prefixedFunctionNames.length; ++i) {

tempDiv.style[property] = prefixedFunctionNames[i] + argString;

if (tempDiv.style[property] != "")

return prefixedFunctionNames[i];

}

return null;

}

 

var linearGradientName = FirstSupportedFunctionName("backgroundImage", ["-ms-linear-gradient", "-moz-linear-gradient", "-webkit-linear-gradient", "-o-linear-gradient"], "(top, black, white)");

var radialGradientName = FirstSupportedFunctionName("backgroundImage", ["-ms-radial-gradient", "-moz-radial-gradient", "-webkit-radial-gradient", "-o-radial-gradient"], "(50% 50%, circle cover, black, white)");

Testing Sites that Use Vendor-Prefixed Properties

A common question is what property names to use if some browsers don’t yet support the property or if no browser supports the standards-based property without a prefix. There are a couple approaches, each with merit:

  1. Always include all expected names, even if they don’t yet work in shipping browsers. The benefit of this path is that as browsers add support with their vendor prefix or add support for the non-prefixed property, your site will “just work” without changes. The risk is that the site will automatically pick up behavior you’ve never tested. A vendor prefix indicates the behavior isn’t finalized and all prefixed properties and the non-prefixed property may not behave the same, so as browsers add support your site might “just not work.”
  2. Only include property names you can test. The benefit is that your site will behave the same as when you first wrote it even as browsers add support for new properties. The risk is that you have unnecessarily degraded functionality. For sample or demo sites, people can interpret this as a browser not having a feature at all.

You need to determine the right path for your site. In most of our demos we want to show off new Web platform functionality in any browser that supports it. And since small errors in these demos don’t create big problems for users, we usually choose option #1. On the other hand, if you have a production site where a change in behavior will cause a problem for your business, you may opt for the more risk-averse path.

Regardless of which path you choose, the one constant is testing. When using vendor-prefixed properties you’re leveraging early, often unstable functionality that can change even after a browser first introduces support for a property, so it’s critical to test with each browser update to make sure your site functions as expected.

—John Hrvatin, Lead Program Manager, Internet Explorer