How to get absolute positioning working with IE6
Introduction
This article documents a way of fudging a web page so that absolute positioning works properly under IE6. The fudge involves a small amount of Javascript. If you are not sure why you would want to do this, read my article on position:absolute.
Possible solutions
There are a number of possible strategies one might choose to handle the problems with absolute positioning and IE6.
- Don't bother supporting it
- Override the CSS so that IE6 works - although possibly not as well
- Provide a dumbed down interface for IE6 and other limited devices
- Apply the javascript fudge described here.
1. Is really the simplest and cheapest option. Sadly, at the time of writing, IE6 is probably the second most popular browser out there. (Below IE7, but still above Firefox.)
2. IE6 supports absolute positioning, but not of the bottom-right
corner of a block. It's also woerth remembering that IE6 doesn't support
min-width or max-width. One option would be to make a
fixed width version of the wb-site specifically for IE6.
3. even ignoring IE6, there maybe other browsers out there, that don't support absolute position, or that have small screens that make a complex interface unwise. It might be worth providing an alternative style-sheet that gives users a very stripped down interface to the web-site
4. Read on...
Javascript Solution
The solution outlined here involves a javascript library that attempts to provide a generic answer. The library only affects IE and only versions prior to version 7. The general strategy is:
- Design your site so it works without code in a good, well-behaved browser (eg Firefox or IE7)
- Check it on IE7, Firefox and so on
- Add the
absolutefudge.jslibrary - Link to it and ensure the code gets called - but only for IE6
- Check your site on IE6
So first you design to the standard (and those browsers that support it). Then you add some fudge to get it to work in IE6.
Using absolutefudge.js
If you want to try some of my fudge please do. Just download absolutefudge.js and play. It's released under GPL.
The first step as already mentioned, is to design your web page so that it works on a browser that fully supports absolute position mode. IE7 or Firefox would do. Most browsers work in different modes, 'quirks' or 'standards', depending on, amongst other things, the DOCTYPE. I tend to use the transitional version of XHTML1.0. It's probably a mistake to try and get this to work with older DOCTYPES, you could end up chasing all sorts of browser quirks mode issues.
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
The <?xml declaration is more problematical. It shouldn't make any
difference whether you include it or not for Firefox or IE7. But in IE6, it's
behaviour is, to put it mildly counter-intuitive. Omitting it, will set
standards mode, including it will put IE into quirks mode. I've tried to
code absolutefudge.js so it works in either mode. So include or
exclude it, depending on which works best. (But don't comment it out, that
produces an invalid XML file that confuses IE7.)
Once you have a page working in FF/IE7, with a suitable DOCTYPE, simply
add the absolutefudge.js code. You will need to include the
javascript file and also arrange to call the apply() function. This code
should only be included for IE6. The simplest way to do this is to use
Microsoft's conditional comments. Other browsers will ignore this - which
is exactly what we want.
Place this in the <head> section of the web-page:
<!--[if lt IE 7]>
<script type="text/javascript" src="absolutefudge.js"> </script>
<script type="text/javascript">
function onLoad() {
if (AbsoluteFudge != undefined) {
AbsoluteFudge.apply(document.getElementsByTagName('body')[0], true, true);
}
// other IE6 loading stuff...
}
</script>
<![endif]-->
Then somewhere below the <body> element, insert this script.
<!--[if lt IE 7]> <script type="text/javascript">document.body.onload = onLoad;</script> <![endif]-->
Now, check that the page still works under FF/IE7. Next, try it under IE6. Hopefully, apart from minor tweaks everything should work.
Limitations
A few limitations of this technique. Firstly, the good news is, the code never actually gets called in IE7 or Firefox. And hopefully that will be true for future browsers. So including this code should be 'mostly harmless'.
A big limitation with this code, is that it can't handle sizes unless they are set in pixels (px). That's rather a shame. Some times it would be useful to set the size of one block to a given number of pts or ems. However, trying to work out from within Javascript the current conversion ratio of pixels to points or ems would have been more work - and at the time I didn't need it.
The simplest work-around that I can think of is to set the size in ems/pts for IE7, Firefox et.al and then put a reasonable value in, in px for IE6.
div#someBlock {
position: absolute;
left: 4px;
width: 10em;
...
}
* html div#someBlock {
width: 140px;
}
This example of CSS uses the '* html hack' to provide IE6 with a fixed size. The bug this hack uses has been fixed in IE7 so it only affects older versions of IE.
Another shortcoming is that my code doesn't cope that well with different amounts of padding and different border-widths. It can also be quite difficult to get the dimensions right if padding/border-width is non zero. This is especially the case if you end up using quirks mode in IE6.
Still to do
Looking at this code again, I think a couple of improvements might be in order.
A possible improvement would be to move the test that checks if left and right are set to auto. This could be moved to the set() or apply() routine. The test would then only be performed once. The same also applies to the equivalent test in the fudgeHeight() routine.
However, this may have a few drawbacks. I'm not entirely sure that the values
for left / right will always be valid at
onLoad() time. Also, you might have other Javascript code that changes the
visibility, position or size of an element.
Finally, I haven't really worked out the padding and borders properly. This is mainly laziness on my part.
The final section is a line-by-line examination of the Javascript code. Non programmers might want to look away now!
Code walk-through
First we define a global variable, to which all the routines and variables can be attached.
var AbsoluteFudge = new Object();
This object acts as a namespace for all the absolute fudge routines.
The first function is a simple utility function to indicate if an element is absolute or not.
AbsoluteFudge.isAbsolute = function (element) {
return (element.currentStyle.position == 'absolute');
}
AbsoluteFudge.set = function(element) {
if (element) {
element.style.setExpression("height", "AbsoluteFudge.fudgeHeight(this)");
element.style.setExpression("width", "AbsoluteFudge.fudgeWidth(this)");
}
}
The second function, set(), is slightly unusual. It uses an IE specific function, but that is OK because we only want it to work in IE. IE allows you to replace a style with a Javascript function. Once set, the browser will call the routine itself whenever the element might need to be redrawn.
The next function, apply(), provides a way of calling the set() function, not just for a given element but also for it's children. If recurse is true this function walks the tree. If full is true it does the entire tree. if howerver, all your absolute positioned elements are at the top of the HTML element tree, you can set this false. It will then stop walking down the tree whenever it encounters a non absolutely positioned block.
AbsoluteFudge.apply = function(element, recurse, full) {
for (var i = 0; i < element.childNodes.length; i++) {
var child = element.childNodes[i];
if (child.nodeType == 1) {
if (this.isAbsolute(child)) {
this.set(child);
if (recurse) {
this.apply(child, recurse, full);
}
}
else {
if (full && recurse) {
this.apply(child, recurse, full);
}
}
}
}
}
The next two routines, then handle the actual adjustment of width and height of the given element. Note: that once apply()/set() have been called, these routines get called automatically by the browser. I didn't have to code that bit.
These routines are the same except one does width and one does height. I will only describe the fudgeWidth() function.
AbsoluteFudge.fudgeWidth = function(el) {
if ((el.currentStyle.right != 'auto') && (el.currentStyle.left != 'auto')) {
try {
var parent = el;
var w = 0;
do {
parent = parent.parentNode;
w = parent.clientWidth;
}
while (w == 0);
if (document.documentElement.clientHeight != 0) {
// in standards mode so width that gets set is inside padding/margin
w = w - parseInt(el.currentStyle.paddingLeft)
- parseInt(el.currentStyle.paddingRight);
if (el.currentStyle.borderLeftStyle != 'none') {
try {
w = w - parseInt(el.currentStyle.borderLeftWidth);
}
catch(e) {
w = w - 2; //estimated
}
}
if (el.currentStyle.borderRightStyle != 'none') {
try {
w = w - parseInt(el.currentStyle.borderRightWidth);
}
catch(e) {
w = w -2; //estimated
}
}
}
el.style.width = (w - parseInt(el.currentStyle.left) -
parseInt(el.currentStyle.right)) + 'px';
}
catch(e) { }
}
}
The first thing the function does is check whether the left/right
attributes are set to a size. This code only needs to apply when
both left and right are set.
The next few lines of code walk up the tree, from the current element, looking for a node that has it's width set. Basically, we need a value for the width of the parent block but the immediate parent might not have one, as it might be an inline element or set to auto. So we climb up the tree until we find a numeric value.
The next block of code is only run if the browser is in standards rather than quirks mode. Checking the clientHeight is an easy way to do this. Remember, none of this code is ever run except in IE6. In quirks mode, IE considers the width to be the overall width including any padding or border. In standards mode, it follows the W3C rules. If the browser is in standards mode, we (crudely) estimate the padding & borders and remove this from the available width.
Finally, the routine does the calculation. It sets width to be
the overall width minus left and right.