Absolute fudge

This article was written some years ago, when IE6 was widely used. So, as of early 2012, I've made a few revisions. New revisions are in bold and out-of-date stuff has been crossed out. Hopefully no one actually wants to support IE6 any more!

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.

  1. Don't bother supporting it
  2. Override the CSS so that IE6 works - although possibly not as well
  3. Provide a dumbed down interface for IE6 and other limited devices
  4. 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.)This has got to be the preferred option!

2. IE6 supports absolute positioning, but not of the bottom-right corner of a block. It's also worth remembering that IE6 doesn't support min-width or max-width. One option would be to make a fixed width version of the web-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 solution. The library only affects IE6. It doesn't even get loaded for other browsers and only versions. The general strategy is:

  1. Design your site so it works without code in a good, well-behaved browser (basically anything other than IE)
  2. Check it works cross-browser and in newer versions of IE (7 and above)
  3. Add the absolutefudge.js library
  4. Link to it and ensure the code gets called - but only for IE6
  5. 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.)

These days the simplest option is to use an HTML5 DOCTYPE declaration. Don't bother with an xml declaration, it's quirky in various versions of IE.

<!DOCTYPE html>

Once you have a page working in modern browsers, 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.


{{item.description}}