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:
- 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.” - 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