来看一个经典的三栏布局:

从内容出发(渐进增强的核心思想),一份合理的HTML结构如下:

<div id="page">
<div id="hd"></div>
<div id="bd">
<div class="main"></div>
<div class="sub"></div>
<div class="extra"></div>
</div>
<div id="ft"></div>
</div>

main是主要内容,sub是辅助内容,比如导航、相关文章等,extra是额外信息,比如广告等,具体含义根据实际情况来定。书写HTML文档有个非常重要的原则是:重要的内容放前面。这能让文档更富语义,能提高可访问性,对SEO也更友善。

写好上面的代码后,我们来看看如何实现三栏布局。(书写HTML时,尽量以内容为向导,要避免一上来就考虑布局,这体现的是内容与表现的分离,同时也是渐进增强思想在工作流程中的体现。)

Table布局

经典得一塌糊涂的表格布局,在渐进增强面前落花流水——表格布局要求书写HTML代码时,首先考虑布局,而不是内容。不啰嗦,直接枪毙。

Float布局

浮动布局满天飞:

优点是简单,缺点呢?在One True Layout这篇文章中有详细的探讨。或者看我收集的一些Float引发的Bugs.

考虑到IE的庞大市场,目前只好将浮动布局关禁闭了。

绝对定位布局

绝对定位非常简单,非常精准。但面对不可预料的高度时,绝对定位就死翘翘了。可以看看这篇文章,还有一个不错的例子:Flanking Menus.

废话不多说,也关禁闭。

负边距布局

Ryan Brill在2004写了一篇文章:Creating Liquid Layouts with Negative Margins. 文中的“发现”立刻让布局世界迎来了“负来负去”的新时代。对于上面的三栏布局,需要先调整DOM结构:

<div id="hd"></div>
<div id="bd">
<div class="content">
<div class="sub"></div>
<div class="main"></div>
</div>
<div class="extra"></div>
</div>
<div id="ft"></div>

最终的CSS实现请看这里。请仔细阅读Ryan的原文,可以发现负边距能解决两栏布局中文档结构的问题(main可以放在sub前面)。但对于三栏布局,Ryan给出的解决方案需要添加额外的包裹层,对DOM结构也依旧存在依赖关系。

这个方法的最大价值是:开启了负边距的神奇大门,让布局的实现思路立刻活跃了起来。

圣杯布局

2006年,Matthew Levine开始寻找布局圣杯:In Search of the Holy Grail. 这是一篇让人赞叹振奋的文章。Matthew灵活运用容器的内边距、浮动元素的负边距和相对定位,接近完美的实现了三栏布局。DOM结构也很好,无需额外标签。但是,我们来看看多达27页的评论吧。圣杯布局最令人头疼的是:在IE6下,左栏经常会神奇消失!!!(比如将IE6的窗口高度拖小点)评论中还反馈在IE7下也存在不少问题(我测试后,发现在IE7正式版下没问题,评论中的IE7可能是beta版)。另外在Chrome下表现也有点诡异,需要小小hack. 还有那繁琐的padding, margin, left, right计算,时刻需要一颗清晰的大脑,喝点小酒就彻底晕了……

这里还有篇文章专门分析了圣杯布局在IE6下的Bug: Jump on hover in Ala’s Holy Grail layout.(万恶的IE6啊,浪费了我们多少宝贵的时间)

2008年11月4日补充:这里又一个改进版的圣杯布局,解决了所有问题,缺点是包裹层太多,唉。

总之:这是一个很美妙的布局,但在IE6尚未退出历史舞台的当今,圣杯布局可能并不是我们真正要找的圣杯。

伪绝对定位布局

聪明的同行们对完美布局的追求孜孜不倦。2008年我们迎来了奥运。Eric Sol 给奥运的献礼是一个聪明的布局尝试:Faux Absolute Positioning.

这个布局思路很简单:先相对定位到最右边,再用margin-left移过来。关于这个布局,曾经引发了淘宝UED内部的热烈讨论。若干月后,我和明城在不同的项目中采用了这一方法,结果发现在ie下,某些页面会闪屏(页面加载时能看见左移)。当时项目紧,没细究,上周想重现却怎么也重现不了(明城说页面非常复杂的时候会闪屏,但具体原因没找出来)。今天看原文的评论,有人指出在IE6下,设置背景图会导致这个布局彻底完蛋

仔细想来,这个布局最让人担心的是:为什么一开始要将所有itemleft: 100%? 这个太邪恶了,让人不放心。

“借尸还魂”的Table布局

乍一看,这个布局很雷人:基于display:table的CSS布局。作者作了解释:在css里使用table-cell之类的声明,仅是声明渲染方式,并不影响HTML文档中的语义。从这个角度讲,这种布局方式的确不错,而且很容易就可以做到等高,也不用考虑清除浮动等扰人的问题。

但是,又是IE成了绊脚石。在IE中,这个布局需要IE8才支持。不过,即便所有浏览器都支持了,我为什么老觉得有点“借尸还魂”的感觉?

更多

布局世界精彩纷呈,下面这些文章也非常有影响力:

  • Eric的Any-Order Columns, 对于固定宽度的三栏布局,Eric的方案非常优秀。
  • 大名鼎鼎的One True Layout, 里面很多总结性质的研究,非常值得一读。

小结

可以看出,有不少布局(比如圣杯布局、伪绝对定位布局、One True Layout等)都符合渐进增强的工作流程。圣杯布局和One True Layout里,都把negative margin发挥到了极致(让我对浮动元素的负边距有了彻底的了解)。伪绝对定位布局则让人很不放心left: 100%,用来解决原文中的问题感觉不错,但如果用来作为整个页面的布局,则有点邪恶。

布局中究竟有没有圣杯呢?下一篇中将给出我的尝试。

上篇中讨论了各种已有的布局实现。仔细分析各种布局的技术实现,可以发现下面三种技术被经常使用:

  • 浮动 float
  • 负边距 negative margin
  • 相对定位 relative position

这是实现布局的三个最基本的原子技术。只要巧妙组合,并加以灵活运用,就能“拼”出各种布局的实现方案。

尝试之路

考虑以下DOM结构:

<div id="page">
<div id="hd"></div>
<div id="bd">
<div class="main"></div>
<div class="sub"></div>
<div class="extra"></div>
</div>
<div id="ft"></div>
</div>

利用浮动元素的负边距来定位:

.main {
float: left;
width: 100%;
}
.sub {
float: left;
width: 190px;
margin-left: -100%;
}
.extra {
float: left;
width: 190px;
margin-left: -190px;
}

这样我们得到了第一个尝试页面 pe_layout_example1.html.
可以看出,通过简单的负边距,已经让subextra定位到正确的位置。剩下的问题是如何让main也定位到正确的位置。

一个自然的想法是,给main的容器#bd添加padding:

#bd {
padding: 0 230px 0 190px;
}

效果请看: pe_layout_example2.html.
这样能让main定位到正确的位置,但subextra的位置不对了。这是一个思考的关卡。既然subextra的位置不对,那就想办法调整到正确的位置。相对定位隆重登场:

.sub {
float: left;
width: 190px;
margin-left: -100%;
position: relative;
left: -190px;
}
.extra {
float: left;
width: 230px;
margin-left: -230px;
position: relative;
right: -230px;
}

demo页面: pe_layout_example3.html. 很明显,这就是圣杯布局!

组合这三种基本技术,我们可以继续尝试各种想法。比如伪绝对定位布局(这个布局不难想到,难的是第一个想到),类似的还有逆伪绝对定位布局(先都移动到最左边,然后再margin-right一个个移过来)等等。

在不增加任何额外标签的假设上,我尝试了各种想法,但始终都没找到完美的布局实现(圣杯布局是我觉得所有想法中最接近完美的)。

既然不添加额外标签时,完美布局的实现如此困难,那如果允许添加一个额外标签呢?在淘宝UED内部的探讨中,给main增加了一层包裹:

<div id="main" class="column">
<div id="main-content">#main</div>
</div>

里层main-content的作用就是将main定位到合适的位置,并方便设置padding等属性。想到此处,就像牛顿被苹果砸傻了一样,原来的main定位问题迎刃而解:

<div id="page">
<div id="hd"></div>
<div id="bd">
<div class="main">
<div class="main-wrap"></div>
</div>
<div class="sub"></div>
<div class="extra"></div>
</div>
<div id="ft"></div>
</div>

CSS仅需增加一行:

.main-wrap {
margin: 0 230px 0 190px;
}

demo页面: pe_layout_example4.html.

一切如此简单!除了添加了一个额外标签,其它各方面,表现都很完美(试了下IE5.5, 也没任何问题)。目前只用到了浮动和负边距,如果再引入相对定位,还可以实现三栏布局的各种组合:

.extra {
float: left;
width: 230px;
margin-left: -100%;
position: relative;
left: 190px;
}
.main-wrap {
margin-left: 430px;
}

demo页面: pe_layout_example5.html.

仔细查看example5example4的源代码,可以发现DOM结构是完全一样的,仅仅CSS稍有不同。这意味着HTML结构和CSS布局在一定程度上解耦了,我们开发HTML代码时,从内容出发即可,无需过多的考虑布局。这正是渐进增强在前端工作流程上的体现。

如果把三栏布局比作一只大鸟,可以把main看成是鸟的身体,subextra则是鸟的翅膀。这个布局的实现思路是,先把最重要的身体部分放好,然后再将翅膀移动到适当的地方。因此请容许我给这个布局实现取名为双飞翼布局(Flying Swing Layout).

就如上图中的鸟有各种姿势一样,利用双飞翼布局,我们也可以实现各种布局。这里有个尝试页面,利用双飞翼,实现了一套栅格化布局系统

优点

  1. 实现了内容与布局的分离,即Eric提到的Any-Order Columns.
  2. main部分是自适应宽度的,很容易在定宽布局和流体布局中切换。
  3. 任何一栏都可以是最高栏,不会出问题。
  4. 需要的hack非常少(就一个针对ie6的清除浮动hack:_zoom: 1;
  5. 在浏览器上的兼容性非常好,IE5.5以上都支持。

不足

  1. main需要添加一个额外的包裹层。
  2. 等待你的发现与反馈。

补充

双飞翼布局的想法与实现受了圣杯布局UED内部讨论的PPT的启发。尝试后发现一切如此简单,都有点奇怪为什么网络上一直没有文章来阐述。

前些日子主要精力都放在了阅读ALA上的文章,没怎么注意其它信息。昨天才仔细阅读Eric的Any-Order Columns和Alex的One True Layout, 发现这种思路和想法早就有人尝试过了。比如Eric原文中的例子是定宽的,但稍微修改,就可以演化为双飞翼布局。Alex的One True Layout, 给的例子被墙了,就一直没细看,今天才找代理过去瞄了一眼,一瞄不要紧,原来One True Layout就是双飞翼,不过Alex只用到了浮动和负边距,因此没有提及main - sub - extra这种排列的实现。

此外,中午还有一个非常震惊的发现:Alessandro早做了一个很详细的页面Layout Gala, 列举了40种布局,用的就是双飞翼!

巧合让人有点沮丧,但更让我高兴。因为Alex和Alessandro的工作,证明了这种布局的普适性。因此不用像采用伪绝对定位布局时一样,得担心新技术带来的风险!可以说,双飞翼布局已经是一个成熟的布局,但因为Alex的被墙,以及Alessandro的宣传力度不够,导致这个布局被我重新“发现”了一次。特撰此文,并取名为“双飞翼布局”,希望这个布局能让更多的人知道,并应用于实践中。