A few entries back, I wrote about CSS Computed Style. In that blog entry, I discussed a cross-browser javascript function to retrieve the computed style of a DOM element on the page.
If you need a quick refresher… javascript doesn't allow you to access arbitrary style properties of a DOM element via .style unless you've previously set that property using javascript OR if you've defined the style using an inline style attribute of the DOM element.
This is where CSS Computed Style can really come in handy. By retrieving an element's computed style, you can retrieve style properties just as they were when you defined them in your CSS file (regardless of whether or not you've ever set them in javascript).
But there's a gotcha (isn't there always). If you remember, IE doesn't really support computed style. Instead, IE offers .currentStyle which, while similar, is most definitely NOT the same thing.
Perhaps the most inconvenient shortcoming of .currentStyle is the fact that it fails to normalize all units to pixels, as the W3C recommends.
In this blog entry, I'm going to explain how to write a function that will convert these non-pixel values to pixels for you.
Read on to learn more.
Writing a convertToPixels(..) function is actually rather tricky. When I first set out to do this, I failed to account for the fact that many of the units we deal with in modern CSS are relative units. For example, em, ex, or % are all units that have no meaning without context.
As you'll see shortly, this has serious implications for the technique I'm going to use for this function.
The basic concept:
The basic concept for this function is fairly simple. We'll pass in a string to our function, something like '5em' or '12pt'. We'll then create an invisible <div> element, set either its height or its border, add it to the page, and then measure it using .offsetHeight to get a pixel value.
The full code for the function is below, with an explanation to follow:
if (!sstchur.web) { sstchur.web = {}; }
if (!sstchur.web.xb) { sstchur.web.xb = {}; }
sstchur.web.xb.convertToPixels = function(_str, _context)
{
if (/px$/.test(_str)) { return parseInt(_str); }
var tmp = document.createElement('div');
tmp.style.visbility = 'hidden';
tmp.style.position = 'absolute';
tmp.style.lineHeight = '0';
if (/%$/.test(_str))
{
_context = _context.parentNode || _context;
tmp.style.height = _str;
}
else
{
tmp.style.borderStyle = 'solid';
tmp.style.borderBottomWidth = '0';
tmp.style.borderTopWidth = _str;
}
if (!_context) { _context = document.body; }
_context.appendChild(tmp);
var px = tmp.offsetHeight;
_context.removeChild(tmp);
return px + 'px';
};
The first thing you should notice about this function is that we first test whether or not the string that was passed into the function is already in pixels units (i.e. it ends with 'px'). If it does, we'll simply return that value right back (no sense "converting" what doesn't need conversion.
Next, we create a <div> div element and set a number of style properties:
.style.visibilityto 'hidden' so that this element never shows up on the page..style.positionto 'absolute' so that the element won't shift other elements around on the page..style.lineHeightto '0' because IE will add mysterious white-space to our element that will throw off our measurement if we don't.
The next part is a little tricky. If the units we're trying to convert have been specified as a percentage, then we need to set the height of our invisible <div>, and adjust the _context element to be .parentNode of the _elem that was passed in (% generally always means "percent of the parent element"). However, if the units are anything else, then it's best to leave our element's height as 0, and set it's top border only.
Why? Well, trying to set the border of an element as a percentage isn't going to work. You might think then, that we should just use the height technique all the time (I thought that at first). The problem with this however, is that IE seems to think the a non-specified border is 'medium'. Trying to set the height of an element to 'medium' isn't going to be very useful.
The long and short of it is this: A border can be almost anything (px, em, ex, pt, a keyword, like 'medium' or 'large') but it can't be a percentage, so if we're dealing with a percentage, it's best to set the height and leave the border alone.
{ tmp.style.height = _str; }
Now, if the unit is not a percentage, then we'll use the border technique. This means setting the element's top border to the specified value and explicitly setting the bottom border to 0 (we're not interested in the right or left borders so we can safely ignore them). We also need to remember to set the border style to 'solid' or else this technique won't work as expected.
{
tmp.style.borderStyle = 'solid';
tmp.style.borderBottomWidth = '0';
tmp.style.borderTopWidth = _str;
}
We're almost ready to measure our invisible element, but there's one thing we must do first.
If the units we're trying to convert are relative units, then the equivalent pixel value depends upon context. In other words, '10%' when specified on an element that is 200px would be 20px. But that same '10%' when specified on an element that is 500px would be 50px. If we want the right value, we need to consider this context.
That is why convertToPixels(..) takes an optional second parameter, _context. If this parameter is not specified, we'll default to the <body> element, but if it is specified, we'll use it.
The preceding line of code ensures that the _context variable won't be null (and thus wreak havoc on our function).
Finally, we append the element to the _context element, measure its .offsetHeight, remove it from _context and then return the value we just measured.
var px = tmp.offsetHeight;
_context.removeChild(tmp);
return px + 'px';
Why add the + 'px' to the value you ask? Well, the major goal here is to make the cross-browser getComputedStyle(..) function we wrote to act more like a W3C style function when used in IE. Since W3C browsers would return the value with 'px' tacked on to the end, we'll go ahead and do this manually in our function so that we remain consistent.
Now that our convertToPixels(..) function is complete, it would be good if we modified getComputedStyle(..) to take advantage of it.
We'll do that in Part 2.
Pages: 1 2
4 Responses
This has been very helpful. Thanks!
This is perfect: I'm writing a image replacement solution that will benefit hugely from this. Thanks for sharing it!
This returns height of the element in pixels.
In firefox 2.0.0.14 and IE 7.
document.getElementById('div1').scrollHeight
It seems to me that the browsers are implemented the way to complicate the things as much as possible. You must cheat all the time :-))
Vlado,
Thanks for the tip. I've only run across .scrollHeight once or twice and haven't really utilized it much. Do you know if it give the overall height of an element, including borders and padding? Or just the internal height of the element (minus borders and padding)?
What does IE6 think of this property?