jQuery Plugin to Convert CSS Pixels to em, pt, percent and other Units
This article presents a small jQuery plugin to convert the dimensions of HTML objects to various CSS units, such as em and percent. The code is based on a popular implementation from the internet and fixes a subtle problem with the original conversion calcuation.
Tags: jquery javascript web
Previous Works
Scott Jehl posted an interesting solution on his blog a couple years ago. Unfortunately, it is not quite correct and only works in certain special cases. To illustrate the issue, the original code is shown below:
$.fn.toEm = function(settings){
settings = jQuery.extend({
scope: 'body'
}, settings);
var that = parseInt(this[0],10);
var scopeTest = jQuery('<div style="display: none; font-size: 1em; margin: 0; padding:0; height: auto; line-height: 1; border:0;"> </div>').appendTo(settings.scope);
var scopeVal = scopeTest.height();
scopeTest.remove();
return (that / scopeVal).toFixed(8) + 'em';
};
An invisible test div is created with a height of 1em. The embedded style ensures that no CSS properties that may affect the size of div are being inherited. Since the div‘s font size is 1em, the height of the div will be exactly 1em as well. jQuery returns the height of the div in pixels. Since it is known that the div is also 1em high, the pixel parameter that is being passed into the function can be converted to em‘s by simply dividing it by the measured pixel height. So far, so good.
The trouble starts when the font-size of the enclosing container, i.e. the body is modified in a way that leads to rounding errors in the div‘s calculated font size. For example, let us assume that the size of the font that is chosen for the body is 16 pixels. If the body‘s font-size property is changed from 1em (default) to 0.9em, this will lead to a font size of 14.4 pixels. Remember, however, that pixel font sizes are integers, and that there is no such thing as 0.4 pixels in CSS. Therefore, the div‘s font-size
property will actually be 14 pixels instead of 14.4 pixels – an error of almost 3 percent.
If we now wish to measure the dimensions of another object that is sufficiently large, i.e. a div that is 144px wide, and convert its width to em‘s, the result will also be about 3 percent off and return 10.285em instead of the expected 10em (144 pixels at 14.4 pixel font size equals 10 em).
Implementation
The resolution of the conversion is directly dependent on the resolution of the test div‘s font size. Therefore, in order to retrieve more accurate results, we simply have to create a much larger test div. If the desired resolution is 0.001em, the test div‘s font-size property should be increased to 1000em, and if the resolution is to be ten times larger than that, it should be increased to 10000em. The larger the font-size, the higher the resolution.
The following code has been modified from the example above to allow for a resolution of 0.0001. It has also been adapted to allow for passing in a unit string to allow for converting to any of the CSS units, including cm (centimeter), em, ex, in (inch), mm (millimeter), pc (pica), pt (point), px (pixel) and % (percent), so that it is not limited to just em‘s.
$.fn.toUnit = function (unit, settings) {
settings = jQuery.extend({
scope: 'body'
}, settings);
var that = parseInt(this[0], 10);
var scopeTest = jQuery('<div style="display: none; width: 10000' + unit + '; padding:0; border:0;"></div>').appendTo(settings.scope);
var scopeVal = scopeTest.width() / 10000;
scopeTest.remove();
return (that / scopeVal).toFixed(8);
};
Download Sample
A small sample that shows the original, as well as the new jQuery plugin in action can be downloaded here (33 kB). Adjust the style sheet with different values for the body’s or div’s font size to see the effects of rounding errors.