A List Apart

Menu

Conflicting Absolute Positions

by Published in Browsers, CSS, HTML, Layout & Grids · 77 Comments

On two separate occasions this month, I’ve been required to produce a layout in which a fixed-width scrolling side “pane” and flexible scrolling main “pane” must resize to fill all available space in a browser window.

Issue № 241

As a CSS purist, I’ve always tried to avoid such dynamic layouts, but sometimes they’re required. When they are, I like to have a good old grumble about the fact that I’ve resorted to using JavaScript for my layouts.

The most advanced way of achieving such a layout is to use a JavaScript toolkit such as DOJO—but for what I was trying to achieve, even DOJO felt too bloated and seemed liable to create further complications.

We can, of course, achieve these layout goals by using JavaScript to resize the divs every time the page is loaded or resized. Unfortunately—among other headaches—that’s made more complicated by the choices between window.innerHeight and document.documentElement.clientHeight and document.body.clientHeight, and the need for cross-browser event listeners.

It seems that using JavaScript is an attainable but inelegant solution for this layout. What I really want is a lightweight, easy-to-understand, pure CSS template…

The problem with percentages

We often use elements that have dynamic widths and heights by defining those attributes as percentages. But there’s a problem with percentages: they don’t play well with others. Despite everyone’s best attempts, you just can’t mix up pixels and percentages (although I’ve read rumors that it’s in the cards).

While we can create relatively effective layouts using just percentages,  we can’t then have a fixed-width side panel or a fixed-height header. So percentages just aren’t going to do the job in our layout.

The nature of divs

Adopting a tried and tested philosophical technique, I went back to the basic assumptions to try and find something else I’d missed. I realized that I thought the following statements about divs were all true:

In all browsers

  1. A div is rectangular.
  2. Only one corner of a div can be absolutely positioned on a page.
  3. The location of the diagonally opposing corner must be determined by the width and height of the div.
  4. If the width and height are dynamic, they must be determined using JavaScript.

It struck me that assumption no. 2—that only one corner of a div can be absolutely positioned on a page—should be very easy to either confirm or deny. What happens if we absolutely position a div by defining its top, left, bottom, and right properties, all at the same time? After all, although I think of it as being a “conflict,” it’s actually perfectly valid CSS.

Assigning “conflicting” absolute positions

I had assumed that if you assigned top, left, bottom, and right properties that most browsers would simply ignore two of those properties.

It seemed like a fair assumption at the time. I expected some variation between the browsers, but I also expected all of them to ignore two of the four positions.

I was utterly wrong about that. What actually happens is something rather magical. In every browser I tested, with the exception of IE5 and IE6, all four rules are obeyed. The result is that the div is effectively “stretched” to fill the viewport.

DIV example
fig. 1, DIV size is computed in most browsers when absolute positioning is specified.

A bit of research revealed that I’m certainly not the first person to discover this.

“In browsers that support CSS you can specify all four edges and let the browser compute the width and the height. Unfortunately this doesn’t work in Internet Explorer…”
Autistic Cuckoo
“Technically you can use both a right and a left property and leave the width as auto. But unfortunately IE does not support this…”
css-discuss

In general, this little bit of CSS trickery seems to have been discarded due to its incompatibility with IE5 and IE6, and as a result has remained largely unnoticed, although it’s nice to see that IE7 now supports these “conflicting” absolute positions.

Incompatibility doesn’t make the discovery useless. Our original statements may still apply to IE5 and IE6, but we now have a different set of statements for all other browsers.

In all browsers except for IE5 and IE6

  1. A div is rectangular.
  2. All four corners of a div can be absolutely positioned on a page.
  3. If the location of diagonally opposing corners has been determined the width and height is implied.

An alternative solution for IE

What does this mean? It means our assumption that only one corner of a div can be absolutely positioned on a page creates a problem specifically for IE5 and IE6. As it turns out Internet Explorer actually offers us its own alternative. Earlier on, I said that you can’t mix pixels and percentages, but that wasn’t strictly true: you can in Internet Explorer.

Using the power of dynamic properties, it is now possible to declare property values not only as constants, but also as formulas.

Dynamic properties are undeniably powerful, but as they’re only supported in IE, they tend to be of little real use.

But, once again, incompatibility isn’t a reason for discarding this little trick. Being able to determine the width and height of our divs as a formula means we can specify “the width of the page minus 40px.” As long as we can do that in IE5 and IE6, we can modify our original assumption #4 just a little bit and settle on our final set of statements.

In IE5 and IE6

  1. A div is rectangular.
  2. Only one corner of a div can be absolutely positioned on a page.
  3. The location of the diagonally opposing corner must be determined by the width and height of the div.
  4. The width and height can be determined using dynamic properties.

In all other browsers:

  1. A div is rectangular.
  2. All four corners of a div can be absolutely positioned on a page.
  3. If the location of diagonally opposing corners has been determined, the width and height is implied.

Ah! Now that’s much more like it. As long as all of the above statements are true, we really should be able to put our entire template together using (almost) pure CSS.

Putting it all together

Our wonderfully simple HTML looks like this:

<body>
  <div id="header">
    <p>Our header</p>
  </div>
  <div id="side">
    <p>Our side panel</p>
  </div>
  <div id="right">
    <p>Our main panel</p>
  </div>
</body>

In modern browsers

Our CSS for all modern browsers is now strikingly simple.

  1. We specify the width and height of the body as 100%. (This is actually only needed for our Internet Explorer solution, but there’s absolutely no harm in including it in our main CSS.)
  2. We hide the overflow in the body and html because we never want to see those scroll bars again.
  3. We set the overflow to “auto” for the left and right panels, and hide it in the header.
  4. The header has a width of 100% and a constant height of 80px.
  5. For the side panel we specify the top (header height + padding), left (padding), and bottom (padding) positions. Then we give it a constant width of 200px.
  6. For the right panel we specify the top (header height + padding), left (padding + side panel width padding), right (padding) and bottom (padding) positions.

All of that is very easily translated into the following CSS:

<style type=”text/css”>html {
  overflow: hidden;
}body {
  overflow: hidden;
  padding: 0;
  margin: 0;
  width: 100%;
  height: 100%;
}#header {
  padding: 0;
  margin: 0;
  position: absolute;
  top: 0px;    
  left: 0px;
  width: 100%;
  height: 80px;
  overflow: hidden;
}#side {    
  padding: 0;
  margin: 0;
  position: absolute;    
  top: 100px;
  left: 20px;
  bottom: 20px;
  overflow: auto;    
  width: 200px;    
}#main {
  padding: 0;
  margin: 0;
  position: absolute;    
  top: 100px;
  left: 240px;
  right: 20px;
  bottom: 20px;
  overflow: auto;
}</style>

Creating the exception for IE5 and IE6

In IE5 and IE6 the bottom and right attributes of the main and left panels are just ignored.

This means that the top left corner is still pinned in place for each of our divs, and we just need to define our widths and heights.

We want the height of both the main panel and the side panel to be 100% of the height of the page minus the header height and the top and bottom padding (100%-80px-20px-20px).

We want the width of the main panel to be 100% of the width of the page minus the width of the side panel, the left padding, the right padding, and the gutter padding (100%-200px-20px-20px-20px). The width of the side panel is a constant, and has already been defined, so nothing needs adding here.

By using a conditional comment we can include these expressions for IE5 and IE6. (Line wraps marked » —Ed.)

<!--[if lt IE 7]><style type="text/css">#side {
  height:[removed]document.body.clientHeight-120); »
 /* 80+20+20=120 */  
}#main {
  height:[removed]document.body.clientHeight-120); »
/* 80+20+20=120 */  
  width:[removed]document.body.clientWidth-260); »
/* 200+20+20+20=260 */    
}</style><![endif]-->

Don’t forget: we specifically had to set the height and width of the body to 100% for this to work, but we didn’t need to hide that from other browsers, so it’s included in the main style sheet.

Beautiful

And there we have the finished layout.

Okay, those dynamic expressions aren’t valid, but they are at least hidden from the browsers that don’t need them. Although they’re presented as CSS those dynamic expressions are in truth JavaScript, and as such they won’t work in IE5 and IE6 if JavaScript is turned off.

But then, none of the alternative solutions would work in that situation either.

{Although this technique was developed independently, an article that suggested many of the same methods was published in 2004 by Alex Robinson. —Ed.}

Known issues

There’s a small and annoying bug in Opera 8. Although the side div resizes correctly when the page first loads, it doesn’t dynamically resize when the window size is changed.

This seems to be because we’ve given it a constant width, and I have, so far, been unable to find a way around this issue. Happily, it’s fixed in Opera 9, and it isn’t a particularly critical bug to begin with.

77 Reader Comments

Load Comments