最近在内部讨论关于”完美三栏”的话题,看到一篇”In Search of the Holy Grail“,相当的好.故此翻译之.

In Search of the Holy Grail


很抱歉我没有将这篇文章命名.我不是想夸大他的重要性或是轻视其他的Holy Grails.但是确实是这么称呼,我们都明白它的含义.

三栏,一个是固定宽度的导航栏,另一个是GOOGLE广告,或是Filckr图片展示,就像Fancy的松露巧克力一样,和一个重要的柔滑的夹心。在这个博客流行的黄金年代它是相当适用的,加之相当大的实现难度,理所应当的获取了”圣杯”的名称.

有很多文章是讨论关于”圣杯”的,也有很多很好的模板可以使用。但是,所有的方案都是以牺牲以下特性为代价的:合理的内容顺序宽度自适应合理的标签。这三者往往是这难以达到合理的布局中要折中的元素

在最近的一个项目中我终于找到了传说中的圣杯。我在不会改变您的代码和灵活性的前提下尽可能的描述他。他会是:

  • 一个自适应适应的中心和固定宽度的侧边栏
  • 允许中间的内容先于其他出现在代码中
  • 允许任何一栏都是最高的高度
  • 只需要额外的一个DIV标签
  • 非常的简单的CSS代码和很少很少的HACK 补丁

站在巨人的肩膀上

这个技术的灵感来自于Alex Robinson’s的One True Layout。他曾经在他的文章里阐述过”圣杯”的若干问题,但是他的解决方法需要两个包装并且要求每一栏都需要一个父级DIV,否则很难写内在结构.
另一方面则是由Eric Meyer’s的写法想到的,他利用了多种类型的单元混合定位。它的例子中也是用了固定的侧边栏和自适应的中心层,可是不幸的是,他依赖于近似的百分比,不是固定的值,而且填充了一部分随屏幕分辨率不同而自适应宽度的层。

言归正传,看看代码

代码是很直观而且很优雅的。
(为了直观起见,我们使用了非语义化的”中心”、”左”和”右”来阐述我们的代码,但是我们建议您在您的代码中使用语义化的标签 -Ed.)

1
2
3
4
5
6
7
<div id="header"></div>
<div id="container">
  <div id="center" class="column"></div>
  <div id="left" class="column"></div>
  <div id="right" class="column"></div>
</div>
<div id="footer"></div>

一个额外的DIV包含着我们的三个层,这样的结构符合我集中内容上一体的标记为一体的习惯(obsessive compulsive markup habits. 翻译的不准确)
样式很也简单,左边侧栏是200PX,右边是150,为了简便标示用LC,RC,CC分别代表左边,右边和中间,样式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
body {
  min-width: 550px;      /* 2x LC width + RC width */
}
#container {
  padding-left: 200px;   /* LC width */
  padding-right: 150px;  /* RC width */
}
#container .column {
  position: relative;
  float: left;
}
#center {
  width: 100%;
}
#left {
  width: 200px;          /* LC width */
  right: 200px;          /* LC width */
  margin-left: -100%;
}
#right {
  width: 150px;          /* RC width */
  margin-right: -150px;  /* RC width */
}
#footer {
  clear: both;
}
/*** IE6 Fix ***/
* html #left {
  left: 150px;           /* RC width */
}

重新度量你想要的模型的价值,你会发现其实很简单.这布局能在Opera, Firefox, and IE6(需要在最后一句HACK).IE5.5则需要HACK CSS盒模型。刚好也给读者一个练习的机会(Orz).
再看一遍这段优化代码(例子)

原理
其实诀窍很简单,让左边栏与右边padding,右边栏与左边padding,中间刚好剩下自适应的内容层.
让我们一步一步来演示这个过程
第一步:创建父级容器
包括header, footer, and container.

1
2
3
4
5
<div id="header"></div>

<div id="container"></div>

<div id="footer"></div>

我们让container(包容的容器)padding-left,padding-right,padding的值分别是左边栏和右边栏的值.

1
2
3
4
#container {
  padding-left: 200px;   /* LC width */
  padding-right: 150px;  /* RC width */
}

我们的布局大致是这样的:

第二步:添加栏
我们现在已经有了父级容器,现在来插入里面的三栏

1
2
3
4
5
6
7
8
9
<div id="header"></div>

<div id="container">
  <div id="center" class="column"></div>
  <div id="left" class="column"></div>
  <div id="right" class="column"></div>
</div>

<div id="footer"></div>

现在我们要添加宽度和浮动属性以使他们在一行,并且在footer清除浮动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#container .column {
  float: left;
}
#center {
  width: 100%;
}
#left {
  width: 200px;  /* LC width */
}
#right {
  width: 150px;  /* RC width */
}
#footer {

  clear: both;
}

你会留意到中间的层已经和外面的层一样宽了(除去padding值的情况下),一会我们就可以看见所有的层都在一起并且也是100%宽。现在,层的顺序就是我们想要的顺序了,但是因为中间的层占据了100%的宽度,所以左边和右边的层就掉了下来。

第三步:把左边栏放在左边
现在我们要干的是怎么样让这三栏在一行,中间的容器已经完全符合我们的要求了,我们只需要关注剩下的两个,先从左边的开始。
需要两步让左边栏在左边.第一步:让他以100%的负margin(margin-left: -100%;)穿过中间的层,记住一定要用100%,因为中间的层的宽度是100%.

1
2
3
4
#left {
  width: 200px;        /* LC width */
  margin-left: -100%;  
}

现在左边栏和中间栏重合,左边重合,右边栏浮动过来(虽然还是掉下去了),现在的结果变成了下面的样子:

第二步:现在要把左边栏拉到正确的位置,我们使用相对定位来抵消左边栏的宽度

1
2
3
4
5
6
7
8
9
#container .columns {
  float: left;
  position: relative;
}
#left {
  width: 200px;        /* LC width */
  margin-left: -100%;  
  right: 200px;        /* LC width */
}

因为设置了right: 200px;所以左边栏被中心层的右边推远了200px,刚好到了左边。(The right property pushes it 200px away from the right edge; that is, to the left.)现在左边栏刚好完美的到了他应该到的位置

第四步:把右边栏放到右边
剩下的事就是把右边栏放在右边,需要把他拉出容器放在容器的padding上,我们依然利用负边距。

1
2
3
4
#right {
  width: 150px;          /* RC width */
  margin-right: -150px;  /* RC width */
}

现在所有的东西都在他该在的位置上了,没有谁”掉队”,呵呵。

第五步:补充设计
如果浏览器重新调整大小,中间的容器小于左边栏的时候,整个布局都会被破坏掉,所以要设置一个min-width来保持我们的布局不被片破坏,虽然在IE6上这个属性是不起作用的,但是没关系。

1
2
3
body {
  min-width: 550px;  /* 2x LC width + RC width */
}

没有任何一个布局是不要对IE增加一些额外的工作就能完成的。(-_-!) 在IE6中负边距会使左边栏离的太远(在全宽浏览下),我们要用右边栏的宽大把他向右拉回来。并且用* html来屏蔽其他浏览器执行他.

1
2
3
* html #left {
  left: 150px;  /* RC width */
}

至于为什么要用右边栏的宽度,要牵涉到一些算法。当然我不会用去解释这些,我们只要知道这样是起作用的,我们甚至可以认为这个是IE的众多”魔法”之一。

Padding, please
我不是设计师,但是上面的布局实在是触犯了我的审美观。没有边距的栏目是很分辨和阅读的,我们需要空隙!

使用One True Layout这样百分比布局的的弊端之一就是会造成中间层的padding困难,百分比的padding在某些分辨率下会变的很难看。可以用固定宽度的padding,但是需要给每一栏内部嵌套一个div。

用这个布局padding不是问题,他可以直接给左、右边栏添加padding,只需响应的调整即可。例如我们要给上面的例子添加10px的padding,并且保持他(width+padding)还是200px,只需要小小的改动

1
2
3
4
5
6
#left {
  width: 180px;        /* LC fullwidth - padding */
  padding: 0 10px;
  right: 200px;        /* LC fullwidth */
  margin-left: -100%;
}

要给中心层加padding需要一个技巧,但是不需要额外的结构,只需额外的一小段css。加的100%的宽度导致中心层和外部的的padding以外(non-padded width)的宽度一样宽。为了达到我们想要的效果,我们需要增加右边的margin,使其等于padding值的总和,这样就保证了他会是我们想要的大小。
我们改变了中间层的改变成现在这样后,左边栏需要移动更多的距离才能在正确的位置上,这也是技巧所在。我们需要给右边框的抵消值(上面的right值)增加上中间层的padding值。
为了更具体的说明,我继续以上面的例子为例,现在给每一个边框增加10px的padding值(合计20px),中间层增加20px的padding值(合计40px),新的样式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
body {
  min-width: 630px;      /* 2x (LC fullwidth +
                            CC padding) + RC fullwidth */

}
#container {
  padding-left: 200px;   /* LC fullwidth */
  padding-right: 190px;  /* RC fullwidth + CC padding */
}
#container .column {
  position: relative;
  float: left;
}
#center {
  padding: 10px 20px;    /* CC padding */
  width: 100%;
}
#left {
  width: 180px;          /* LC width */
  padding: 0 10px;       /* LC padding */
  right: 240px;          /* LC fullwidth + CC padding */
  margin-left: -100%;
}
#right {
  width: 130px;          /* RC width */
  padding: 0 10px;       /* RC padding */
  margin-right: -190px;  /* RC fullwidth + CC padding */
}
#footer {
  clear: both;
}

/*** IE Fix ***/
* html #left {
  left: 150px;           /* RC fullwidth */
}

当然上下的padding值能很方便的添加,具体请参看nicely padded version 中的例子
这个布局也能在em下很好的工作,但是不能在混合em和px的时候工作,选什么你来定,但是要选的正确。
等高栏
这个布局能保证所有的栏目都是等高的,实现方式是来着与One True Layout的adapted wholesale。所以我不详细解释,增加和修改代码如下

1
2
3
4
5
6
7
8
9
10
#container {
  overflow: hidden;
}
#container .column {
  padding-bottom: 20010px;  /* X + padding-bottom */
  margin-bottom: -20000px;  /* X */
}
#footer {
  position: relative;
}

我特别在下面留了10个像素

附加的说明,请注意在Opera上存在一个bug,即overflow:hidden会让你所有的栏目都变大,在One True Layout上有详细的解决办法。你可以使用这个方法或是等Opera 9(被修复了这个bug)来测试。
另一个问题是,在IE中如果内容的高度没有背景图片的高度高,背景不会被剪掉,他会超出footer。如果你没有独立的footer或是内容比背景高,这也不是个问题。如果你仍然需要一个footer,不要怕还是有解决的办法的。用一个DIV把footer封装一下就可以。

1
2
3
<div id="footer-wrapper">
  <div id="footer"></div>
</div>

现在依然用上面我们使用到的让各个栏目对齐的技巧来让footer超过封装的DIV,来显示我们想要显示的内容

1
2
3
4
5
6
7
8
9
10
11
12
* html body {
  overflow: hidden;
}
* html #footer-wrapper {
  float: left;
  position: relative;
  width: 100%;
  padding-bottom: 10010px;
  margin-bottom: -10000px;
  background: #fff;         /* Same as body
                               background */

}

现在解决了所有的问题,得到了我们想要的结果和很少量冗余的代码。

哦,还需要说明的
完美主义者有可能是想知道是否有一个更好的方式来做到这一点?我之前声明过,我引用了一个非语义化的包含容器(DIV),确实,我们不应该包含一个额外的结构来打乱我们完美的结构.
如果你像我一样,象知道这样怎么去实现,不需要更多的结构,我向你介绍”wrapper-free Holy Grail (没有包含的圣杯)”,其最抽象的特殊之处在于,用一个DIV实现了各部分 — 不多也不少,语义化,不愧于”圣杯”的美名.其原理是相似的.主体直接应用padding不需要多余的容器,而用负边距来延伸header和footer 使其刚好达到想要的宽度.
这种布局能在所有的浏览器上正常工作,甚至(令人震惊)是在IE上,但是不是等高的.而且在非常小的窗口中是会”破掉”的,使用他的时候一定要谨慎.
最后
虽然文中提到的例子很具体,但是这项技术的使用范围确实很大.为什么不能有两个活动的中心层,为什么不调换层的顺序.这些引用都超出了本文的表述范围,但是要实现他们只需要很小的改动即可.使用圣杯是明智的.他可以成为你使用CSS的技巧之一.

Your Name
Your Email
Your Website (optional)
2008-11-06

思路还是值得学习的。

popo
2008-11-07

学习了
以前一直是用float left right
然后剩下的为中间的内容

2008-11-07

这有严重的性能问题,尤其在IE下。作为布局系统,不允许啊(position: relative; overflow: hidden)。 :)

2008-11-07

的确很cool~

最妙的还是这段

#container .column {
padding-bottom: 20010px; /* X + padding-bottom */
margin-bottom: -20000px; /* X */
}

apple
2008-11-07

这个马桶确实不怎么滴,看到那个图片就想起马桶。
圣杯变马桶貌似不怎么滴!

嘿嘿,阿布辛苦了!让我有了俯视老外的机会!

2008-11-12

是三列自适应等高且中列宽度自适应布局吧

本来两种效果分开的话,都还简单。结合起来就颇费些周折了。
(我的做法在ie6之外都还简单,针对ie6就需要一点点hack了)

margin-left/right的负值很有创意啊

只是为什么在我的ie7下显示有问题啊

kookoo
2008-11-26

对于我来说还是值得学习的,转载了

little_fleur
2008-12-05

有个问题请教一下:按照这种布局是两个侧边栏固定宽度,中间宽度自适应;那么是不是意味着中间一栏里的布局也全部要使用百分比?否则在另一个分辨率下是不是会显得很奇怪?对于复杂页面,目前我还不是很习惯使用百分比去布局,请赐教哈,谢谢~

fay
2008-12-05

#left {
width: 200px; /* LC width */
right: 200px; /* LC width */
margin-left: -100%;
}
是不是应该改为
#left{width:200px; right:200px; margin-right:-100%;}

colinli
2008-12-14

这篇文章–http://www.alistapart.com/articles/fauxabsolutepositioning(伪绝对定位)的方法更好,不用hack就能实现这个功能。

2009-01-22

IE6下面,中间的没有一个最小宽度,一缩放就失真= =!
OP和safari下面显示也不正常。
不知道这些是不是我的个别原因还是。。。至少在我这里是这样有bug…

2009-02-04

哇。好简单的东东,看都看不懂

2009-05-23

.col{
padding-bottom: 20010px; /* X + padding-bottom */
margin-bottom: -20000px; /* X */
border:1px solid;

}

dawdaw

dwd

dadwdawsss
da

也这么用过,呵呵

2009-05-23

.col{
padding-bottom: 20010px; /* X + padding-bottom */
margin-bottom: -20000px; /* X */
border:1px solid;

}
<div style=”background:red;overflow:hidden”>
<div style=”width:200px;background:yellow;float:left” class=”col”>dawdaw

</div>
<div style=”width:200px;background:yellow;float:right” class=”col”>dwd<br>

</div>
<div style=”width:auto;background:yellow;margin-left:200px;margin-right:200px” class=”col”>dadwdawsss
<br><br><br><br><br><br>da
</div>
</div>
忘转义了…

calm
2009-07-20

IE6下确实有问题。

yadan
2009-07-28

为什么我用IE7显示例子一 http://www.alistapart.com/d/holygrail/example_1.html ,左边栏的内容都没有出来呢? 到这里 http://ipinfo.info/netrenderer/index.php 查询IE6,7,8的现实都有问题?

.

yadan
2009-07-28

http://www.alistapart.com/d/holygrail/example_3.html 在IE7下拉伸了很长? 是我的IE的问题,还是?

2010-01-28

这个方法确实是很不错的一个方法,只是会有局限性。。
假设左边两列的高度都要自动延伸,左边是红色,右边是灰色,中间内容变高了,左边的红色不会延伸下来,右边的灰色同样也没法延伸。