CSS Layout Mystery
Among the many two (or three) column layout methods, I sometimes use the following:
<div class="left1"> <div class="left2"> left main content </div> </div> <div class="right1"> <div class="right2"> right sidebar </div> </div> together with:
.variant1 .left1 { float: left; margin-right: -200px; width: 100%; } .variant1 .left1 .left2 { margin-right: 200px; } .variant1 .right1 { float: right; width: 200px; } This works in all major browsers. But for some very strange reason, exactly the same method, but the opposite, does not work :
.variant2 .left1 { float: left; width: 200px; } .variant2 .right1 { float: right; margin-left: -200px; width: 100%; } .variant2 .right1 .right2 { margin-left: 200px; } In the second option, you cannot select the text in the sidebar and so that all links cannot be clicked . This is at least true for Firefox and Chrome. In IE7, links can at least be clicked, and Opera looks completely fine.
Does anyone know the reason for this strange behavior? Is this a browser error?
Please note: I am not looking for a working technique for arranging two CSS columns, I know that there are many of them. And I don’t need this technique to work. I just like to understand the reason why the second option behaves.
Here is a link to a small test page that should illustrate the problem: http://selfthinker.org/stuff/css_layout_mystery.html
This is a major separation problem. Let's analyze the CSS specification for visual formatting to find out why.
The order in which the rendering tree is drawn on the canvas is described in terms of stacking contexts.
Well, let's see what the “stacking context” we are in. To do this, we need to know when a new stacking context is created.
[Z-index of] value ... integer is the stack level of the generated field in the current stacking context. The field also sets the local stacking context in which its stack level is "0".
Well, we have no z-index values, so they are all automatic.
The root element forms the context for laying the root. Other stack contexts are generated by any positioned element (including relatively positioned elements) having a computed z-index value other than "auto".
No, there are no positioned elements either. Looks like we're all in the “root stacking context”.
Boxes with the same stack level in the stack context stack in the reverse order according to the order of the document tree.
This certainly explains why .right1 draws over .left1 - after it in the original order. (Note that you will see a paint problem better if you remove margin-left: 200px from .right2 ).
So now that we know the problem (and that it meets the specification) - how do we fix it? The easiest way is to make z-index .left1 be higher than .right1 . Since they are in the same stacking context, a higher z-index will override the source order:
.variant2 .left1 { position: relative; z-index: 1; }
Or, if we continue to read the specification, we will notice that:
Each stacking context consists of the following stacking levels (from start to finish):
- The background and borders of the element that forms the styling context.
- Stacking contexts of descendants with negative stack levels.
- a stacking level containing undirected descendants without an inline level.
- laying level for non-positioned floats and their contents.
- stacking level for unsupported inline-level descendants.
- the stacking level for positioned children using "z-index: auto" and any child stack stacks with "z-index: 0".
- descendant stacking contexts with positive stack levels.
which means we can just do:
.variant2 .left1 { position: relative; }
which will give .left1 "stacking level" of 6 - which will override the .right1 stacking level of 4.