Three column stretching

This script is not yet ready for prime time. It works fine on this page, except in Explorer 5.0 Windows and Safari 1.0, but on more complicated pages it may misfire in a number of subtle ways which I haven't investigated yet.

The tiny script presented on this page begins to solve a common CSS problem: stretching three floated columns to the height of the highest column. The script isn't yet ready for real use, but it nicely illustrates the principle of minimal CSS enhancement.

CSS has a few very annoying blanks that may frustrate your attempts to create a nicely designed pure XHTML/CSS page. I'm thinking of the lack of vertical alignment, or the stretching up of several floated columns to the height of the highest one.

In my opinion JavaScript can lend a helping hand. However, we should avoid the interminable and useless DHTML libraries that scourged web development in earlier, less enlightened eras. Instead, we should establish the principle of minimal CSS enhancement.

The purpose of such a script is to help smooth out rough edges on CSS by applying the minimal necessary change to achieve an effect. Ideally, this means tweaking only one single style value, as this example script does, and leaving the rest to normal, simple CSS.

Once we've written such a simple script, accessibility issues become manageable. If the script doesn't work the CSS problem will not be solved, but since these solutions usually fall in the nice-to-have category and are in no way necessary for the basic functioning of the page, I don't see any fundamental reason not to use JavaScript to help CSS along.

For a more fundamental and extensive discussion, see Bobby van der Sluis's Presentational JavaScript article.

Example

This example doesn't work under all circumstances, and therefore it is not ready for prime time. Please read all browser notes below on this page before mailing me about it.

 
This is the left column

This is a classic example of a three column layout. The left and right columns have only a little content (in a real website they'd have more than just one line of text, but still less than the main column).

Ideally, all these three columns should have the same height. Of themselves, the left and right columns have "as much height as they need" (ie. height: auto) for reasons I'll explain later on.

Nonetheless graphic designers are less than thrilled by this lack of stretching, and rightly so. Pure CSS just doesn't give us the possibility to solve this problem. Do we embrace the purity argument and leave the CSS as it is, or do we use JavaScript to solve the problem?

This is the right column
 

The styles

The three column layout above is very simple:

<div id="testmain">
	<div class="left">
		This is the left column
	</div>
	<div class="mid">

		[text]

	</div>
	<div class="right">
		This is the right column
	</div>
	<div class="clearer">&nbsp;</div>
</div>

The HTML is styled as follows:

.left {
	float: left;
	background-color: #732264;
	width: 15%;
	color: #ffffff;
	height: 100%;
}

.mid {
	float: left;
	width: 50%;
	height: 100%;
	margin: 0 15px;
}

.right {
	float: right;
	background-color: #B0BDEC;
	width: 15%;
	height: 100%;
}

.clearer {
	clear: both;
	font-size: 1px;
}

As you see I floated the three columns and gave each of them a width. I also gave each of them a height: 100%, and that's the key to the effect.

height: 100% means: make the height 100% of that of the parent element. In this case the parent element is <div id="testmain">, and that's where the problems start. Since I do not know in advance how much text the middle column will contain, nor how wide the user's browser window will be, I cannot give this parent element a height (well, I could, but it'd be ugly in 99.9% of the cases).

Therefore the parent element has height: auto, ie. "as much as you need". The problem is that the CSS specification says that any block with a percentual height that is contained by a block that has height: auto should also get height: auto. In other words, the height: 100% does not apply to naturally stretched blocks.

JavaScript to the rescue

Superficially this is a very serious problem. To enable the height: 100% we'd have to give the container block a specific height, but how do we know what value it should have, when we don't know how much text the middle column contains and how wide the user's window is?

The answer, of course, is reading out the real, rendered height by JavaScript and setting the style.height to this value:

function threeColumn() {
	var elm = document.getElementById('testmain');
	elm.style.height = 'auto';
	var x = elm.offsetHeight;
	elm.style.height = x + "px";
}

We read out the offsetHeight of the containing block, which contains the actual height the block has in the rendered page. We then add "px" to this value and paste it in style.height.

Now, all of a sudden the container block has a specified height, and therefore the height: 100% of the three columns kicks in. The three columns stretch themselves to completely fill their container and the effect has been established.

As to the elm.style.height = 'auto';, this line is necessary if the function is called more than once. If we have to recalculate the height of the container, we should first reset it to auto, to allow it to stretch naturally, and read out the new offsetHeight only afterwards.

This script solves an annoying problem by tweaking one single style value and leaving the rest to regular CSS definitions. Obviously, if JavaScript is disabled the CSS value is not tweaked, but in that case we're not worse off than we were before. The page is not inaccessible if the three columns don't have equal height, it's just less beautiful.

Browser incompatibilities

Unfortunately the tiny script above is only a first approximation of a solution. Cross-browser reality is more obnoxious, to say the least.

Most of these problems can be solved by fundamental research into offsetHeight and its changing, but at the moment I don't have time for it.

Clearer element

First of all we need a clearer element. As you see the last element inside the container is

<div class="clearer">&nbsp;</div>
.clearer {
	clear: both;
	font-size: 1px;
}

All browsers except for Explorer require it. The reason is that without this element the container div contains only floated elements, which means that its normal height defaults back to 0. We don't want that, we want the container to stretch up as much as necessary so we can read out its actual height.

The clearer solves this problem for us. It is the last element in the container, it is not floated, hence it stretches up the container so that it actually contains the floater, and thus the three columns above it.

As to the single &nbsp;: Mozilla requires it (don't ask me why). If it's not there the trick doesn't work. I set the clearer's font size to 1px to hide the &nbsp; as much as possible.

Resizing

If you resize this page you'll see that the column height doesn't change. The obvious solution to this problem is

window.onresize = threeColumn;

However, this solution turns out to work only in Mozilla and Explorer Mac. Explorer Windows and Opera make the container larger and larger and larger with each resize, and I'm not yet sure why.

Resizing and Explorer Windows

You'll notice that when the page becomes narrower, and the container block higher, Explorer Windows nicely changes the height of the columns, too. This does not happen when you make the page wider and the container block less high.

The cause is Explorer's peculiar interpretation of overflow: visible. If the content is too large for a block to contain, Explorer stretches up the block to contain all content, even if the block has a set height. Therefore it correctly handles any increase (but not decrease) in container height without further JavaScript aid.

Box model

Browser differences in box model will certainly cause serious problems. This example entirely avoids the issue by not applying any paddings or borders, but for real-world applications these problems must be solved.

Opera: not fast enough

If you call the script more than once, you'll notice that Opera doesn't behave right. As far as I can see this is because it doesn't take the time to calculate the effect of resetting the height to auto before calculating the new offsetHeight. Therefore the offsetHeight remains what it was, and the columns do not react properly.

The same issue may bug Explorer on Windows if we call the script onresize, but I haven't yet investigated this.

Opera: extra offset

You may note that in Opera the container element becomes slightly larger each time you run the script, even when you don't resize the browser window. I'm not yet completely sure of the cause, but in tightly designed pages this may become a problem.

Explorer Windows: large image problem?

When I tried this script in a page where one column contained a large image, Explorer added the height of this image to the height of the container element, and the columns became much too high. I don't yet know why, but it means that this technique is not yet usable in all pages.

Explorer 5.0 Windows: float problem?

The default styles of my example are very weird in Explorer 5.0 Windows. I assume this is because of a problem in the handling of float, but I haven't yet investigated it. In any case the example doesn't work in this browser.