Clearance

I recently revisited Alex Robinson’s One True Layout while preparing for an upcoming speaking gig for AIGA Baltimore. After playing with Any Order Columns I was still disappointed by the fragile nature of float-dependent layouts. One text size increase too far or an image too wide and bam! there goes the neighborhood. Even something as simple as italic text can cause floated columns to wrap in IE Win.

Wrapping floated columns

I’ve never been completely satisfied with my method of clearing absolutely positioned elements either though. It’s a hack and it feels like it. That said, I still think absolute positioning is the way to go. So this weekend I revisited the idea, starting over from scratch.

Rethinking the Problem

Absolute positioning is by far the most reliable way to position something with CSS. Take the elements that require positioning and wrap them in a containing element. Set that containing element’s position to relative. Then set the original elements’ positions to absolute and specify top and left coordinates. These positioned elements can be crammed full of gigantic images and italic text or have their text resized until the x-height matches the window height and they still won’t wrap—unlike our fair-weather friend, the float.

Regarding IE Win

It’s worth noting that IE Win can act unreliably when the positioning context is a certain element (usually inline elements). However using the Holly Hack (assigning a dimension to the element, usually height: 1%;) to enable hasLayout makes quick work of that bug.

Gotchas

Unfortunately once we position those elements absolutely, they are taken out of the flow of the document and the containing element collapses. This results in elements below the containing element sliding up behind the positioned elements.

The two columns overlap the footer

We can avoid this pitfall by positioning the tallest contained element relatively (using the same top and left coordinates). That leaves the element in the document flow, effectively side-stepping the overlap issue—as long as it is always the tallest element—which can be a gamble in a production environment.

The two columns clear the footer

So, let’s say the anticipated element really is the tallest one sixty percent of the time. That would result in overlap the other forty percent of the time.

The second column overlaps the footer

What if we use JavaScript to determine when we’ve made a mistake and simply correct it? Instead of looping through all the contained elements, looking for the tallest and setting the containing elements’ height to match, the function would determine the tallest contained element and change its positioning to relative and its siblings to absolute.

The second column no longer overlaps the footer

Absolutely Final (For Now)

That’s just what the new SI.ClearChildren object does. To use, add the following rules to your style sheet:

.clear_children,.cc_tallest { position: relative; } /**/* html .clear_children { display: inline;}/* PREVENTS MISSING CHILDREN IN IE WIN 5.0 */
.cc_tallest:after { content: ''; } /* PREVENTS A REDRAW BUG IN SAFARI */

Now layout your elements using absolute positioning. These elements should be relative to a containing element with a class of clear_children which contains only the absolutely positioned elements (no other inline elements). This will cause the containing element to collapse.

Then decide which contained element will be the tallest on the most occasions and assign a class of cc_tallest. That will cause the containing element to expand to the height of this element.

Finally include the SI.ClearChildren object JavaScript file right before the closing body tag.

<script type="text/javascript" src="si-clear-children.js"></script>

The object and related functions don’t touch any event handlers so this script should not interfere with third-party or homebred scripts.

Some example usage:

Still a Hack

The foundation of this solution is extremely simple. Position elements absolutely. Change tallest element to relative. This may be something to take to the W3C and browser manufacturers. Imagine if this simple behavior was triggered with the following CSS:

position: inline-absolute;

No JavaScript, no clear_children or cc_tallest classes. Just a simple property declaration in a style rule you have to define anyway. I’ll even write the obtuse property value definition:

The box’s position is calculated according to the ‘absolute’ model (including being removed from the normal flow) unless it is the tallest of it’s siblings in which case the box’s position is calculated according to the ‘relative’ model (and remains in the normal flow).

Until then download the packaged source (offered as is, tested in Internet Explorer 5+, Firefox, Safari 2+, Opera6+.), slather on those classes and daydream of browser support for inline-absolute.

Previous
Alternating Rows in PunBB
Next
Which web development tools do you use?
Author
Shaun Inman
Posted
May 22nd, 2006 at 8:47 am
Categories
CSS
Design
JavaScript
Web
Comments
023 (Now closed)

023 Comments

001

This is brilliant.
Column height is one of the most tricky parts of CSS layouts (in my experience at least) to reliably control. I’m going to take a proper look later on but the concepts and demos are lookin’ good.

As for inline-absolute, bring it on. I’m really surprised something similar hasn’t been implemented yet.

It looks like media queries in CSS3 could be used to do this too. Don’t quote me on that, but it certainly looks promising.

Author
Rob Wilmshurst
Posted
May 22nd, 2006 5:35 am
002

Clever rethinking of the solution! I can see some potential gotchas, such as setting up CSS in such a way that flipping an element from absolute to relative interacts with other normal-flow content in unforeseen ways, but there’s only so much you can do before it’s the author’s problem.

Author
Eric Meyer
Posted
May 22nd, 2006 6:23 am
003

I’ve just tested it on iCab 3 beta, Netscape 7.02 and it works well everywhere. In my hacked (so that it runs on Tiger) Safari 1.0, the nested variant shows an overlap in the third position: absolute block in source order on the nested fluid example. Though that’s better than your site which doesn’t work at all in that version of Safari.

I’m not sure I like adding more hacks to the style sheet and requiring JavaScript for a usable layout on corner cases, but could have its uses.

Author
Michel Fortin
Posted
May 22nd, 2006 6:57 am
004

Even if i personally dont like javascript (well im trying to force myself into learning it, I think im going to get DOM Scripting) this looks awesome Nice work!

Author
Fredrik Wärnsberg
Posted
May 22nd, 2006 7:14 am
005

I am really psyched this works in iCab. I will let you know how my tests go with CyberDog today.

Well done!

Author
Mike D.
Posted
May 22nd, 2006 7:41 am
006

Hot stuff, Shaun. Can’t wait to have a play.

Author
Ethan
Posted
May 22nd, 2006 8:16 am
007

Shaun, this is a fabulous solution to ‘that’ age old problem. You say that SI.ClearChildren still a ‘hack’ but floats were never intended as layout tools and they too are a hack. Layout is an issue that has not been fully thought about (IMHO) at the W3C, something that I hope will change as more designers get involved.

My worry with any script-layout solution/fix is what happens when scripting is not available or turned off and so the CSS tumbles to ruin. Would it be feasible/beneficial to tier the CSS by putting the layout structure into a separate file that is loaded only when scripting is available? In that scenario, visiting ‘sans-script’ will give a reader colors and fonts but no layout. This would be preferable to a ‘broken’ layout.

Forgive me please for asking the stupid question?

M

Author
Malarkey
Posted
May 22nd, 2006 12:54 pm
008

Withholding the layout stylesheet from those without JavaScript is a possible solution. Or you could create an emergency stylesheet that declares a fixed height for clear_children that is taller than you’d ever expect your columns to be and then link that stylesheet from inside a <noscript> tag.

Definitely not a stupid question. The lack of satisfactory answer is the primary reason I still consider this a hack.

Author
Shaun Inman
Posted
May 22nd, 2006 1:25 pm
009

This is very nice. One problem I see with the spec you’ve written is that, technically, the tallest element won’t be following the relative-positioning model. It’s position (left, top, width and height) will still be calculated absolutely. It’s effect on the pageflow is what will follow the relative-positioned model.

This issue might be worthwhile to consider if you’re considering a solution that will serve as a model for a future CSS property. Rather than an inline-aboslute property, you may want to consider an “position: container” property, which operates like “position: relative”, except that it will expand to encompass all of its content, rather than collapsing as other positioning models do (the viewport can be said to operate in this manner).

Author
Mark Kawakami
Posted
May 22nd, 2006 1:37 pm
010

I’m pretty sure the spec I’ve written is accurate. What you are describing is how position relative already works. The element is positioned relative to its position in the normal flow. It sounds like you might be mixing up relative and static.

My examples assume (and the script requires) that the containing element only contain positioned elements. That may be the cause of the confusion.

I do like the idea of a single property assigned to the containing element but I don’t think position is the property to use in that case.

Author
Shaun Inman
Posted
May 22nd, 2006 1:55 pm
011

What a good idea, this may help page layout a bit easier. I feel that using some JavaScript is an acceptable solution if it is necessary to make up for any shortcomings in the current CSS spec, and there are reasonable fallback options for those without JS enabled.

Building websites could be so much easier if there was more widespread support for certain features, such as multiple columns in CSS3, or the positioning option as discussed in previous comments. Things would be even easier if certain browsers actually supported common CSS features properly (glares at IE).

Author
Grant Palin
Posted
May 22nd, 2006 7:17 pm
012

Is the provided script assuming the fixed element goes directly under the document root? I’ve tried implementing this, and am getting errors on line 43: this.control = document.body.appendChild(c);. The error is “document.body has no properties”.

My page structure is body->div#page->div#wrapper, the last one being the container element for the 3 positioned columns. Besides the columns, there are no other direct children of the wrapper div. I’m no JS guru, so would the code need some changing to accommodate this structure?

Author
Grant Palin
Posted
May 22nd, 2006 8:40 pm
013

Sounds like you might be including the script in the head of your html document. As a result the body element hasn’t been parsed yet. The script needs to go immediately before the closing body tag.

Author
Shaun Inman
Posted
May 22nd, 2006 9:07 pm
014

Very nice, many thanks for posting. Found a little bit of IE weirdness in the “clear-nested-fluid” example (left column overlaps a tad), but the others are spot-on.

Author
Kel Smith
Posted
May 23rd, 2006 4:03 am
015

Once again Shaun, another wonderful fix! I have a few projects I am going to incorporate this into right now. Saves me a lot of headache. Great work!

Author
Andrew Christensen
Posted
May 23rd, 2006 10:33 am
016

@Malarkey: Wouldn’t the CSS 2.1 table display properties sort 90% or so of the worst layout troubles? Seems like they would to me, but then I probably work on different sorts of layouts than you do.

As to more designer participation in the W3C, is that happening? I have to (shamefacedly) admit I haven’t been paying so much attention to working group membership of late. If so, it’s a good thing. Maybe they’ll finally address my #1 peeve: the lack of a safe and effective font embedding technology. Not holding my breath tho. :-)

re: witholding layout styles from non-JS visitors

It’s doable, but requires further tradeoffs. My understanding of how this would be done is that you’d use JavaScript to add the layout stylesheet to the document at load time (or before). If there’s another way, then my comments may be inaccurate.

Anyway, the trouble is this: if you use document.write to add the stylesheet tag, you (technically) shouldn’t use an XHTML DOCTYPE — document.write is verboten in ‘real’ XHTML. AFAIK, it will work so long as you’re serving your XHTML as text/html but not if you’re serving it with an XML MIME type (and I don’t even want to touch that debate…).

Alternatively, you can use the DOM to add a LINK or even a STYLE element. Trouble is, that fall flat in IE5/Mac. A defunct browser, to be sure, but worth ignoring? Maybe. I do on many sites myself, in fact.

Thinking a bit further, it’s possible you could add a LINK element in markup, and only set it’s HREF attribute via script…haven’t tried that. Might work.

Author
setmajer
Posted
May 23rd, 2006 11:10 am
017

Ack! I had this very same thought a few weeks ago, but I never quite got around to writing about it. Your implementation is slicker though… mine required the footer to be moved around in the dom-tree, and hooked itself on with bottom: 0.

The really brilliant part about this scheme is that it’s almost 100% safe against issues with text-resizing. Unless the tall column contains fixed content (flash movie, image, etc), and another column only slightly shorter contains text, you’re in the clear.

Very nicely done.

Author
Mike Purvis
Posted
May 23rd, 2006 1:16 pm
018

Ah, the obvious solution - put the script element where it’s supposed to be! Still no worky though, but no error messages either. Any other ideas?

Does the container div need to be directly inside the body tag?

Author
Grant Palin
Posted
May 23rd, 2006 2:35 pm
019

No, the spec isn’t quite accurate. Imagine a box has has this styling: “position: absolute; right: 0; top: 0; width: 50%;”… Now, absolutely positioned, this element should extend from the 50% mark of its positioning context to it’s context’s right edge. However, if it were to switch to relative positioning, it would shift to the left, going from the left edge to the 50% mark, because the left will be computed automatically, and “right: 0” in relative positioning means it should be offset by 0 from where the right edge would ordinarily exist.

So if the spec were to change the positioning from absolute to relative, it could cause content to change where it is positioned. Everything works if you always set an explicit left and width, but can fail otherwise, I think.

Author
Mark Kawakami
Posted
May 23rd, 2006 2:47 pm
020

Solved the problem. Turn out I had to slightly adjust the CSS rules provided to resolve specificity issues, since the containing and contained elements have other classes as well.

Now that that’s out of the way, I can work on a new page layout using this technique!

Author
Grant Palin
Posted
May 23rd, 2006 8:55 pm
021

Being a little picky—the page centering chokes in IE. You need to throw in a few ‘text-align:”s in for good measure. Nice page layout though, I really dig the flow/leading of the text.

Author
Matt Brown
Posted
May 24th, 2006 4:38 pm
022

@Mark Kawakami: I agree with Shaun, I like the idea of the property assigned to the containing element. With regards to Shaun’s response, perhaps the best property to use would be “display: container”. Just a thought.

Author
Doug Ramsay
Posted
May 25th, 2006 6:06 am
023

@Mark Kawakami: I think your problem could be solved simply enough if instead of using “top: 0; right: 0;” you used “top: 0; left: 50%;” Ought to work with both absolute and relative positioning.

Author
David Zuch
Posted
May 29th, 2006 10:39 am