What We're Trying to Do
The basic goal is to create a small library that will make it possible to convert any valid value to pixels. This makes it possible to do further calculations using that pixel value.
The Trouble with Units
style attribute on the element in-order-to trick the browser into doing the conversion for you.
The easiest units to convert are the absolute units. These are units that presumably represent real, fixed lengths (but actually don't). In the the real world there is no such thing as a relative inch — an inch is always an inch regardless of the settings on your monitor. On computers it's a little different; the absolute lengths are based on the DPI setting. On the vast majority of computers this is set to 96, which means that 1 inch usually equals 96 pixels. However, this can sometimes change: it's not entirely uncommon for the DPI to be set to 120 which would make 1 inch equal to 120 pixels. This is also already different on mobile devices like the iPhone 4 or newer (although the iPhone currently lies and claims to be 163DPI).
Because DPI is the deciding factor in absolute lengths, it's useful to define the conversions relative to the inch; this makes all of the other conversions easier (thankfully a pixel is always a pixel — except on iPhones). If you know the pixels in an inch, you can easily know the pixels in a pica.
- relative to OS DPI, usually 96px
- 1mm = 1in/25.4
- 1cm = 1in/2.54
- 1pt = 1in/72
- 1pc = 1in/6
- Mozilla millimeters
- 1mozmm = whatever Mozilla feels like
As the name might suggest, font-relative units are dependent on the font settings of the element itself. The most commonly used is the em unit. One em equals the
font-size of the element (except in the case of the
font-size property where it means the
font-size of the parent element). The ex and ch units are rarely used and equal the height of lower-case "x" and the width of a "0" respectively. New to CSS3 — and much more useful than ex — is the rem unit which equals the em of the
html element. Converting rem and em to pixels is relatively straight-forward — all you really need is the element's (or the parent or
font-size — but ex and ch require using the
style attribute as we'll see later.
Presumably useful for flexible layouts, these units are calculated relative to the browser dimensions and are currently only supported in IE9 and Opera. The three units are vh (viewport height/100) vw (viewport width/100) and vm (the smaller of vh or vw). These units change every time the browser is resized.
Perhaps the most complex unit to convert is percentage. The relative length for a percentage can differ based on the CSS property but it is usually based on the width of the parent element. For instance, a
width of 50% means 50% of the width of the parent element. For things like
margin it can get confusing because these also always mean the width of the parent; counter-intuitively,
padding-top of 50% means 50% of the width of the parent element as well, not the height. The best way to figure out what percentage is referring to is to look the property up on the MDN website.
As mentioned above, converting units can be done reliably* by setting the value using the
style property and reading it back using
getComputedStyle function always*** returns values using pixels.
*sometimes **in supported browsers ***usually
How Conversion Works
The code above is the bare minimum necessary to convert pixel units but it has a few problems that will be discussed later. But first, let's look at what we're doing in the code. In the the
toPx function, we can convert any unit to pixels by assigning the value to the elements
style property, reading that value back as using the
curCSS function (which uses
getComputedStyle) and then changing the element's
This method of checking a property, changing it, measuring it and changing it back is commonly referred to as the "awesome hack by Dean Edwards". (The Dean Edwards hack relies on the proprietary
runtimeStyle property because his comment was originally meant to address an IE-specific issue).
getComputedStyle isn't supported in IE 8 and below; they use
currentStyle instead, and it works slightly differently. Specifically,
currentStyle won't convert anything to pixels; it just returns raw values (known as the specified value). For instance, if the
fontSize is set to 10mm, IE8 and below will return it as 10mm instead of 37.795276px. This problem is discussed in more detail below.
In the example above the default property is set to
width ("the awesome hack by Dean Edwards" uses the
left property) because in non-IE browsers
left cannot be set on a
position: static element. Width will reliably return pixel results even when the element is
display: inline in any browser. It should be noted that in IE 8 and below,
left can be reliably set and retrieved on any element regardless of its
Problems with this Method
If browsers were perfect, the code above would work perfectly. However there are a number of bugs that need to be accounted for. Surprisingly, most of these bugs occur in WebKit, not IE.
WebKit and Computed Value and Used Value
WebKit will not convert percentages when they are applied to
right. There is a bug report for the position properties but it isn't clear that the WebKit team intends to fix it. The MDN page for
getComputedStyle explains that the function should report the used value and not the computed value (which is confusing to say the least). The primary difference between the two is that a computed value can sometimes be a percentage (in the case of
text-indent and others). WebKit is apparently using the computed value in
getComputedStyle which the bug tracker suggests is due to a disagreement about the spec. All other browsers are using the used value.
A ticket in the jQuery bug tracker proposes a fix for WebKet returning a percentage for margin. However, as noted above, this bug also applies to positioning properties as well (and
text-indent). The commit for fixing percentage margins in jQuery specifically addresses margins but it could easily be extended to support the other affected properties.
IE 8 and Below and Specified Value
IE8 and below don't support the
getComputedStyle function so the non-standard
currentStyle property must be used. Unlike
getComputedStyle, which is supposed to return the used value,
currentStyle returns the specified value (the MSDN manual calls this the "cascaded value"). The specified value is the value that should be assigned to the property but before it has been converted to an absolute unit. This is more useful than the
style property, which only exposes the inline values for the element;
currentStyle will return values specified in CSS as well as inherited values.
Thankfully IE 8 and below support a few non-standard properties that always return pixel values. The most commonly used property is pixelLeft (this is used in "the awesome hack"), however pixelWidth and pixelHeight work just as well. Opera also supports the non-standard pixel properties. WebKit supports them only for absolute units and Firefox doesn't support them at all. Interestingly, IE9 doesn't support them anymore because they were deprecated in an earlier version of IE.
Every unit (except percentages) is the same pixel value on an element no matter which property it is applied to (except
fontSize). For instance, a
marginLeft of 1em is the same length as a
lineHeight of 1em. Because of this fact, it is reasonably safe to use the special pixel values to convert units in IE 8 and below.
There is no direct way to convert the
fontSize because it could be relative to the parent elements
fontSize in the case of em and percentages. A jQuery bug about differing values for
fontSize points to a fix where using a
left of 1em will always equal the element's current
As noted above, "the awesome hack by Dean Edwards" uses
runtimeStyle for pixel conversions in IE. This IE-specific property is higher than the
style property in the CSS cascade which means that a
runtimeStyle value supersedes a
style value. By copying the
currentStyle value to the
runtimeStyle, "the awesome hack" avoids any potential for a FOUC. This works because changes to the
style property aren't rendered in the browser when a
runtimeStyle is set and the special pixel properties don't pay attention to the
runtimeStyle (because they belong to the
style object), so they can still be used to return the pixel value of the
style property. The
runtimeStyle is only available in IE and isn't useful for unit conversion beyond trying to fix FOUC issues if they appear in IE 8 or below.
Fixing the Bugs
Now that we understand the problems, we need to set about fixing them! The code below contains a patched
The code above adds several fixes to the
curCSS function to correct for the issues mentioned above. It also adds a check to see if the browser is incorrectly returning the current value instead of the used value.
Changes to the curCSS Function
- Correct for IE being unable to reconcile the
fontSizeproperty by converting the
leftof 1em to pixels.
- Check for percentage units in WebKit.
bottom, compute the parent element's inner height and return the percentage.
- For other properties (
text-indent) convert to pixels using the
- Correct for WebKit and Opera returning the computed value "auto" in cases
topon an element that is
- Correct for Firefox returning the specified value when it can't be set in cases like
topon an element that is
Finishing the Job
Now that we've fixed all of the bugs with
curCSS, it's time to make
toPx a little smarter. For instance, absolute units will never change under any circumstances once they've been calculated. This means that once you know how many pixels are in an inch, converting an inch unit requires only simple algebra and you can skip "the awesome hack" altogether. The same goes for em and rem: once you know the correct
fontSize in pixels, a little algebra finishes the job, no "the awesome hack" necessary. This actually covers the majority of cases for units and leaves only percentages as the primary need for mucking with the element's
What Just Happened?
The final code above makes a few changes to the
toPx function as well as pre-computing all of the conversions for the absolute units.
- Pre-calculate the absolute units by converting mozmm and in to pixels using the
toPxfunction and then using using the pixels-per-inch value to store the other conversions.
- Look for a pre-calculated conversion or a rem or em unit to convert the value to pixels immediately.
- For rem and em, choose the correct element to get the
fontSizefrom and get the
fontSizein pixels using the
curCSSfunction and use that as the conversion.
What Is This Good For?
Nothing? It depends.
Anyone familiar with jQuery knows that
$.css will return the correct value for a property on an element but jQuery doesn't directly expose a method for converting units arbitrarily on an element. jQuery will convert units when calculating the start and end values for an animation (and it uses a version of "the awesome hack" to do it) but the conversion performed is limited in scope.
It's not possible to rely on jQuery (1.7.1 as of this writing) to handle unit conversion because it doesn't expose those methods to the API. Further, the optimizations for converting absolute, rem and em units without using "the awesome hack" should provide some speed improvements on any code that needs to rely on conversions. If you are writing a plugin, particularly a polyfill that requires some calculations to be done on a CSS property, properly converting units will allow users to specify values using the units that make sense to them instead of forcing everything to be specified in pixels.
The GitHub repo for Length and Angle units will hold the latest code and also contains a straight-forward Angle conversion library.