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.

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.

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.

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.

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.

Absolutely Final (For Now)

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

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.

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.

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.

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.

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.

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.

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.

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).

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.

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).

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?

@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.

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.

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.

@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.