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 article describes
a pattern our
team has used
to
make
things significantly easier when developing with vendor prefixes. 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 {
}
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.