Friday, August 7, 2009

CSS: Float Layouts

Everything Does Not Float

"Panta rhei," said
Herakleitos, everything is floating. Even if the
underlying philosophy, that everything is constantly changing, very much applies
to the web, it doesn't apply to today's topic. To make an element float we have
to say so explicitly, for instance like this: float:left


When we make an element floating, we ask the browser to shift it
sideways, either to the left or to the right, as far as it will go.


Those who really understand the previous sentence already know half of what
you need to know to use floats. There are three important pieces of information
in that sentence, so let us examine them before we tackle the other, somewhat
trickier, half.


Step Aside Please

The first thing we need to remember is that a floating element is shifted
either to the left or to the right. It is not possible to make an element float
in the centre, something that often is frustrating for beginners. If you think
about it awhile, it's fairly clear why it is so. Why? To answer that we have to
jump ahead of ourselves a bit and see what happens when we create a floating
element in our document.


A common usage for floats is with images. We want to insert an image and make
the text flow around it. With CSS we don't make the text floating,
but the image. If the image has float:left the text will flow on
its right side. If the image has float:right the text will flow on
its left side.


If it were possible to make an image float in the centre, the browser would
have to make the text flow on two sides of it. Contrary to the two
simpler cases, it's far from clear how that would be done. Should the text lines
split in two to make room for the image? Or should the text suddenly split into
two columns? Unless the image is very small, it is also likely that the
available space on either side of the image won't be enough for long words.


The second important piece of information in the basic rule above is that a
floating element is only shifted sideways. We have seen optimistic attempts to
specify float:top in order to move something to the top of the
page.


In order to really understand float theory you have to understand what a
line box means in CSS. Unfortunately, that in turn
requires you to understand what is meant by an inline box, which is
way too complicated to explain in any detail here. If you're interested (and
slightly masochistic) you can try to decipher the explanation in
section 9.2.2 of the CSS 2.1 specification.


An inline box is generated by those elements that aren't block-level, such as
EM. Furthermore, an anonymous inline box is generated by
text nodes inside block-level elements. It's not too hard to imagine that each
block-level element consists of a rectangular box. An inline box can also be
viewed as such a box, although it obeys slightly different laws than its cousin
the block box.


A line box is an imaginary rectangle that contains all the inline boxes that
make up a line in the containing block-level element. It is (at least) as tall
as its tallest line box.


We won't dig deeper into the box theory than this, so we'll entertain the
notion that a line box is an imaginary box that encompasses an entire line of
text and (non-floating) images. The reason for this digression is that we need
to know that the upper edge of a floating box is aligned with the upper edge of
the current line box (or with the bottom edge of the previous block box, if
there is no line box).


It is also good to know that all floating elements automatically become block
boxes. Thus it's not necessary so say display:block for a floating
element. More about that later.


Okay, so what does this mean in plain English? It means that a floating box
can never end up above the upper edge of the line where it's created. Or, in
other words, that a floating element is only shifted sideways, but we knew
that.


The Buck Stops Here

The third important detail in the basic rule above is the words "as far as
it will go". When we float an element it is shifted to the right or to the
left until it reaches the edge of the containing block. If we then float another
element nearby in the same direction, it will be shifted until its edge reaches
the edge of the first floating element. In other words, it will settle beside
the first element. If we float more elements in the same direction they will
stack up, but sooner or later we'll run out of space. We'll look at what happens
then in a moment, but we need another dose of theory first.


What Is Really Happening?

Just like absolutely positioned elements, floats are removed from the
document flow. They don't affect subsequent block-level elements. However, the
line boxes that are generated next to a float are shortened. Think of it as if
they're just making room, horizontally.


Let's say that we have a number of text paragraphs. In the first paragraph we
want to insert a tall, but narrow, image, and we want the text to flow on the
right side of the image. So we set float:left on the image.


The line boxes that are generated by the text in the first paragraph are
shortened so that they start at the right edge of the image and stretch to the
right edge of the paragraph box. But the image is so tall that the text of the
first paragraph ends before it reaches the bottom edge of the image.


The next paragraph, a block-level element, isn't affected by the floating
image. It has been removed from the document flow. Its block box is created at
the usual distance from the first paragraph's block box, with consideration to
margins and all that. Here's the beauty of it: the second paragraph's line boxes
are also shortened, just as in the first paragraph.


And so it goes on until the text lines reach the bottom edge of the image.
Then the line boxes no longer need to be shortened, and the text continues below
the image.


One, Two, Three, Many

Let's look back to the example where we let a number of elements float in the
same direction. For each floating element, our line boxes are shortened, and
eventually there isn't enough space for the next float.


We now see the next important trait of floats: when there is insufficient
space on the line, they are shifted downward until they fit. This can be
extremely valuable in layouts that need to work in different window sizes. If
the columns are floating, they are rendered side by side in a wide window, or
stacked vertically in a narrow one.


Er … Mate, It's Hanging Out

As we just saw, a floating element can extend past the bottom edge of the
block-level element where it was created. A block box is not extended to enclose
its floating children, since that would make it impossible to achieve the smooth
effect we desired in the example above with the tall, narrow image. (A floating
element is extended to enclose its floating children, though.)


Sometimes this is the desired effect, sometimes it's not. Fortunately,
there's a way to cancel it. We can force an element to drop below all floating
elements via the clear property. If we specify
clear:left the element drops below all elements floating on its
left side. With clear:both we can make sure that an element drops
below all floating elements that would otherwise affect it.


For a block box the shift is accomplished by incrementing its top margin
until the upper edge of the element is below the lower edge of all floating
elements created earlier in the document. (Don't be embarrassed if you have to
read the previous sentence several times to understand it, this is tricky
stuff.)


For inline boxes it's even more complicated, but the result is the same.
And floating elements can set the clear property, too…


Why do we need to know all this? Because it has consequences that would
otherwise seem quite inexplicable. Let's say that we want to make sure that an
H2 heading doesn't end up next to a float. So we set
clear:both on it. Let's also assume that we want a certain margin
between any float and the heading, so we set margin-top:1em. It is
now very possible that we get into a situation where the heading butts up
against the float. Why?


Remember that floats don't affect subsequent block boxes. If we hadn't set
the clear property of the heading, it would have been created
1em below the previous block-level element (and the heading would
have ended up next to a left-floating element extending past its containing
block). Since we do set the clear property, the top margin is
increased until the upper edge of the heading is below the bottom edge of the
floating element. But no more!


If we want to ensure some space around a floating element, we have to set the
margins on the floating element itself.


Floating Columns

How can we use floats to create a multi-column layout, and why is this
sometimes better than absolute positioning?


If we enclose each column in a DIV element with
float:left they will appear side by side, just as we expect columns
to do. If we then want a full-width footer to be shown at the bottom, no matter
which column happens to be longest, we only need to set clear:both
on it.


In a floating layout we want to specify the column widths in percents. Here's
some good advice: don't specify the percentages so that they add up to 100%
exactly (e.g. 60/40 or 25/50/25). The slightest rounding error will then cause
the right-most column to drop below the others. Instead, use 60/39 or 25/49/25,
to be on the safe side.


A fully liquid layout has some inherent problems. In modern browsers (read:
anything but IE) we can set a min-width (preferably in
ems) for each column. In a narrow window, one or more columns may
drop below the others, which may not be all that pleasing aesthetically, but
it's often more user-friendly than having to scroll horizontally.


So we solve the footer problem with liquid columns, something that just
cannot be done with absolute positioning. We do, however, have another problem
that is easier to solve with absolute positioning: source order. It is often
desirable to have the main content of the page as early as possible in the
markup. Less relevant things like navigation and ads can come later. But with
floats we are constrained to specifying them in the proper order. If we want a
menu on the left, ads on the right, and the content in the middle, the content
can't come first. Or can it…?


It's possible, but it's not trivial. Let's say that we want the content
first, then the left menu and the ads last. Let's also say that we want to use
the proportions 25/50/25. Furthermore, we want a border between the columns. In
order to achieve this we need to add a couple of extra DIV elements
to group things. The structure will be as follows:




Content








The outer container consists of an inner container (on the left) and the ad
sidebar (on the right). The CSS looks like this:


#outer {
margin:0 25%;
border:1px solid #000;
border-width:0 1px;
}



The left and right margins are set to 25% to make room for the side columns,
and then we add a thin black border on either side.


The inner container consists of the content column (on the right) and the
menu (on the left). The CSS:


#inner {
float:left;
width:99%;
}

#content {
float:right;
width:99%;
}

#menu {
float:left;
width:50%;
margin-left:-49.9%;
}



The inner container itself is thus floating to the left and occupies almost
all of the outer container's width – except for the margins, of course.
The content column is floating within the floating inner container, occupying
most of its width.


The menu floats to the left, so that it ends up next to the floating content
column. Its width is supposed to match the outer container's left margin, which
is set to 25%. But if we specify the menu width as a percentage, it means a
percentage of the inner container's width, not of the page width. The inner
container's width is (almost) half the window width (99% of 100% minus two
margins of 25%), so the menu's width is approximately 25% divided by .5, which
equals 50%. Phew!


As if this weren't enough, the menu also has a negative left margin
that is almost as large as its width. The operative word here is
almost. The negative margin causes the menu, which only has 1% of the
inner container's width to live in, to be shifted outside the inner container
and end up in the outer container's left margin, which we created for that
specific purpose.


So why don't we set the left margin to -50%? Because we can't
move the menu completely out of the inner container. If we do, it won't
"exist" in the inner container anymore, and if the menu column happens to
be the longest, it will overwrite a subsequent footer. Since this technique
causes the menu to actually overlap the inner container a little, we should give
any child elements of the menu a right margin to create some space.


The CSS for the sidebar column is similar to the one for the
menu:


#sidebar {
float:right;
width:50%;
margin-right:-49.9%;
}



The sidebar is after the inner container in the markup. The inner container
is left-floating and occupies 99% of the centre. Since we float the sidebar to
the right and give it a negative right margin, we shift it out into the margin
just like we did with the menu. The width is computed in the same way and as
with the menu we should give any child elements some left margin to prevent
them from overlapping the border.


If we try this now, it won't work at all. Why? Since all components are
floating and thereby removed from the document flow, the outer and inner
containers have no content at all. The remedy for this is to add a couple of
equalising elements that make sure the containers actually contain their
floating children:




Content










The CSS for those elements look as follows:


.cleared {
clear:both;
line-height:0;
}



There shouldn't be any doubt about what clear:both means by now,
but what does line-height have to do with anything? We don't want
these equalisers to take up any space, but if we specify them as empty elements
they will be excluded in some browsers. Therefore we let them contain a
and then we set the line height to zero.


Bugs, Bugs And More Bugs

If you've endured this far you probably realise by now that floating elements
aren't exactly trivial. It should come as no surprise that even browsers have
some problems with them. At least some browsers. We talk, of course, about
Internet Explorer for Windows.


IE/Win has so many
complicated float-related bugs that we can't go into it here. The example in
this article doesn't work at all in IE/Win, but the article is too
long anyway, without venturing into the jungle of bug fixing.


Fortunately, there is a simple solution that fixes many of the
IE float bugs. We saw earlier that all floats become a block box.
The standard says that the display property is to be ignored for
floats, unless it's specified as none. If we set
display:inline for a floating element, some of the
IE/Win bugs disappears as if by magic. No one knows exactly why.
IE/Win doesn't make the element into an inline box, but many of the
bugs are fixed.

No comments:

Post a Comment