You are reading a translation of an old blog post published on my previous blog in French.
[-prefix-free is] fantastic, top-notch work! Thank you for creating and sharing it.
— Eric Meyer
Thanks to -prefix-free, you no longer have to write CSS properties for every browser extension. You only need to write CSS standard properties, and your code is still supported by all browsers. It is not surprising that most code playgrounds now provide this essential library. Let’s take for example the following pen created initially by amos.
See the Pen cblAm by Julien Sobczak (@julien-sobczak) on CodePen.
No prefix -moz
or -webkit
. Under the hood, -prefix-free adds the prefixed properties dynamically and only if necessary depending on your browser. How does this magic work? We will discover it in this article.
-prefix-free is brought to us by Lea Verou and is available on Github. The code presented in this article has been simplified for obvious reasons and must not be used outside this learning context. This article is based on the last version at the moment of the publication of this article.
A First Example
And:
If we inspect the source code inside our browser, on observe small differences:
- 1
- The stylesheet has disappeared and has been replaced by an inline stylesheet. The content is identical with one exception: the use of the prefix
-moz
for the still unsupported propertyborder-radius
.
Let’s Go!
-prefix-free declares two global variables (StyleFix
and PrefixFree
), reflecting how the library is organized around two distinct parts:
- StyleFix is a framework to apply corrections on a CSS stylesheet.
- PrefixFree relies on it to configure a custom corrector that replace unsupported CSS properties using the prefixed properties instead.
We are using the property border-radius
for illustration purposes in this article. This property is available (with the -moz
prefix) since the version 2 of Firefox. The following examples have been tested using Firefox 3.6.
StyleFix
StyleFix applies a series of changes that are called fixers. A fixer is basically a function satisfying the following signature:
Where:
css
is a string containing the CSS code to fix.raw
isfalse
when the CSS is directly attached to an HTML tag.element
is the associated HTML element (<link>
,<style>
or the HTML tag HTML with the attributestyle
).
The function returns the modified CSS.
The registration of a fixer is done using the function register
:
The fixers are then triggered for every stylized element through the function fix
:
Nothing too complicated until now.
Now let’s take a look at what happens when the page is loading. After the DOM is parsed by the browser, StyleFix looks for all tags <link>
, <style>
and the ones declaring the attribute style
. For our implementation, we are going to consider only the tags <style>
but the logic is unchanged for other types of tags.
querySelectorAll
The small subtlety of this code comes from the method querySelectorAll
that returns an object NodeList
. This object supports a property length
and can be traversed using a for
loop, except that we cannot use the common method forEach
. Why? NodeList
is not an array and we need to use a small hack like [].forEach.call(...)
to fix that (see the NodeList
documentation).
We have finished with the object StyleFix
. Here is the final implementation:
Before moving on next section, here is an example of how to use it to convert all stylesheets using a single line:
PrefixFree
If we omit many implementation concerns, we can start with a first operational, minimal version:
- 1
- We focus on Firefox 3.6 for now.
- 2
- We consider only the property
border-radius
. - 3
- We search for every property to replace.
The code reuses the object StyleFix
to register a custom fixer. This fixer replaces unsupported CSS properties with their equivalent. The regular expression allows making a global replacement. In JavaScript, the method replace only replaces the first occurrence (a flag can be defined as the third argument but is currently not supported by the V8 engine).
If we want to run our code on new code, we still have to solve two remaining issues:
- How to detect the browser prefix to use?
- How to identify the properties to replace?
Let’s start with the first question.
Several solutions are possible. We may use Modernizr but -prefix-free use an even simpler solution. The code creates a new HTML element in the DOM and inspects the attribute style
represented in JavaScript by the object CSSStyleDeclaration
. This object lists the values of all CSS properties supported by the browser. So, we just have to memorize the list of all properties starting with -
to determine the prefix and answer the second question by the same token.
- 1
- This line is necessary to find the name of the property as present in CSS. Indeed, in JavaScript, the CSS properties as defined as properties in the object
CSSStyleDeclaration
, and thus must conform to the rules of the JavaScript language (-
is not allowed in an identifier).
We defined two utility functions to convert from one notation to the other:
In the previous example, we used an array of all CSS properties with a prefix. We still have one case to manage: browsers evolve, and standard CSS properties become supported over time (ex: Firefox >= 4 supports both -moz-border-radius
and border-radius
properties). When the standard property is supported, we better had to use it and stop replacing it.
Our rewrite of PrefixFree is now complete:
We have finished the coverage of -prefix-free. Less than 100 lines of code have been necessary to recreate a basic implementation. The complete source code is available here.
- Try to support the tags
<link>
and CSS properties defined using the HTML attributesstyle
. Hint: Retrieve the content of external stylesheets in AJAX. What are the limitations? - Try to support CSS changes done in JavaScript after the initial loading of the page. Hint: Listen events
DOMAttrModified
andDOMNodeInserted
(see the pluginprefixfree.dynamic-dom.js
). - Try to support
@rules
andkeyframe
. Hint: Use more advanced regular expressions.
- StyleFix/PrefixFree is a great example of the approach divide-and-conquer.
querySelectorAll
returns an object of typeNodeList
, which is different fromArray
.- The object
CSSStyleDeclaration
can be used to list all CSS properties supported by a browser.