🚀 关于「圣杯布局」
发布于 8 年前 作者 DotHide 10207 次浏览 来自 分享

引言

「圣杯布局」—— 尽管这是一个很古老的话题了,而且网上早就有许多相关的文章,但作为前端入门和面试的必备知识之一,还是觉得有必要温故而知新一番。尤其是在拜读了「yuanzm」的博客《我是如何同时拿到阿里和腾讯offer的》文章后,更感到对于任何别人的知识至少都需要花点时间自己实践一下,了解一下它的原理甚至是历史,而不应盲目引用和浮于表面。谨以此文表示感谢~

圣杯的由来

Holy Grail

圣杯布局是讨论「三栏液态布局」的实现,它最早出自于谁或许不得而查了,但最早的完美实现是来自于 Matthew Levine 于2006年在「A LIST APART」上写的一篇文章,它主要讲述了网页中关于最佳圣杯的实现方法。

所谓液态布局是相对固态布局而言的,固态布局就是固定值不变的布局,液态就好比在容器里到了一杯水,它可以随着容器宽度的变化而自适应宽度。

在这篇文章中,作者指出了当时的一些实现方式所存在的问题,如:必须按照源顺序(在 DOM 中表现为先写 Left,然后 Middle,最后,Right)等,它将可能导致代码不够灵活,尤其是从 DOM 的载入顺序上来说,中间的内容不能被首先加载。

因此他给出一个方案,它将:

  1. 两边带有固定宽度中间可以流动(fluid);
  2. 允许中间一栏最先出现;
  3. 允许任意一栏放在最上面;
  4. 仅需一个额外的 div 标签
  5. 仅需非常简单的 CSS,带上最少的兼容性补丁

文中还提到了他的这个想法是基于「One True Layout」 和 「 Eric Meyer’s adaptation」两篇文章带来的灵感。

在这里你可以看到「圣杯布局」的最终效果:http://alistapart.com/d/holygrail/example_1.html

实现方式

接下来就言归正传,说下具体的实现思路:

首先我们需要布局的 HTML 代码如下,作者在这里为了便于表达,对标签使用了非语义化的 id,他建议在其他任何正式项目中尽量使用语义化的 id。例如,我们需要实现的是左侧宽度为 200px,右侧宽度为 150px,中间是流动的布局。

<div id="header">#header</div>

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

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

正如他先前提到的,只是加了一层额外的 div 它的 id 是 container。它的 CSS 内容非常简单,具体代码如下:

body {
  min-width: 550px;      /* 2x LC width + RC width */
}
#container {
  padding-left: 200px;   /* LC width */
  padding-right: 150px;  /* RC width */
}
#container .column {
  height: 200px;
  position: relative;
  float: left;
}
#center {
  background-color: #e9e9e9;
  width: 100%;
}
#left {
  background-color: red;
  width: 200px;          /* LC width */
  right: 200px;          /* LC width */
  margin-left: -100%;
}
#right {
  background-color: blue;
  width: 150px;          /* RC width */
  margin-right: -150px;  /* RC width */
}
#footer {
  clear: both;
}
#header, 
#footer {
  background-color: #c9c9c9;
}

/*** IE6 Fix ***/
* html #left {
  left: 150px;           /* RC width */
}

下面我们来分步观察它的实现逻辑:

第1步 建立框架

先写 header, footer 和 container 三个 div

<div id="header">#header</div>

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

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

我们将 container 的内边距设置为左右两边各自的宽度。它看起来就像这样:

Create the frame

第2步 加入三栏

此时我们有了基本框架,可以把三栏塞进去了。

<div id="header">#header</div>

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

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

接着我们给每一栏配上合适的宽度,并将它们设为浮动。同时我们需要清除 footer 的上下环境,以免遭跟上面三栏一起浮动。

#container .column {
  float: left;
}
#center {
  width: 100%;
}
#left {
  width: 200px;  /* LC width */
}
#right {
  width: 150px;  /* RC width */
}
#footer {
  clear: both;
}

注意这里中间一栏的 100% 宽是基于它的父容器 container 的宽度而言的,由于 container 设置了内边距,因此中间栏看起来就处在了网页的中间,但左右两栏由于排在中间栏的后面,且因为空间不够被挤到了中间栏的下面,如下图所示:

Add the columns

第3步 把左侧栏放上去

中间栏已经就位,剩下的事情就是把左右两栏放上去了,接下来我们先放左侧栏。

为了详述过程,这里将分为两个小步骤。首先,我们先将它的外边距设置为 -100%,这样一来,由于浮动的关系,左侧栏就能上位,与中间栏交叠在一起,并占据了左边。而右侧栏由于左侧栏的上位,自动向前浮动到了原来左侧栏的位置。

Pull the left column into place I

接着我们要用到相对定位属性(relative),并设置一个与左侧栏等宽的偏移量:

#container .columns {
  float: left;
  position: relative;
}
#left {
  width: 200px;        /* LC width */
  margin-left: -100%;  
  right: 200px;        /* LC width */
}

可以看到,它设置的 right 属性就是相对于 container 的右边线向左偏移 200px,如此一来,它就完美地跑到了 container 左内边距的位置,也就是我们希望它呆的地方,如下图所示:

Pull the left column into place II

第4步 把右侧栏放上去

最后,我们需要把右侧栏放上去,此时只需利用上面的原理把他放到 container 的右外边距的位置即可,我们需要再一次设置一个负外边距的值,它等于右侧栏的宽度:

#right {
  width: 150px;          /* RC width */
  margin-right: -150px;  /* RC width */
}

至此,所有的栏目都就位了~

Pull the right column into place

源码地址

https://jsfiddle.net/DotHide/pg47fucg/1/

参考文章

《In Search of the Holy Grail》 by Matthew Levine

6 回复
margin-left:-100%;

这个怎么理解

好复杂有意义吗 为什么不使用flux

赞,一种新思路。上面的朋友没有注意到,是先中间加载,再二边加载。 自豪地采用 CNodeJS ionic

@yakczh 以left为例,正的外边距就是它的左边线与左边的其他占位对象的边距,负的外边距就是它的左边线与右边的其他占位对象的边距,由于浮动的缘故只要盒子的边框一跑到父容器的外面,它就能上去,也就是说其实 margin-left 设为盒子本身的宽度 -200px 时它就能上位了。

希望有所帮助~ :)

@DotHide 这个100%是以父容器的宽度为基准,还是以自身节点的宽度为基准算出来?

<!DOCTYPE HTML>
<html lang="ru-RU">
<head>
  <title></title>
  <meta charset="UTF-8">
<style type="text/css">
.list{
width:300px;
height:300px;
background:gold;
border:1px solid crimson;
float:left;
}

#left{
background:green;
height:200px;

margin-right:-100%;
}

</style>
</head>
<body>
<div class='list'> 11111 </div><div class='list'  id="left"   > 2222</div>
<div class='list' style='background:navy' > 33333</div>
<div > 4444 o</div>


  
</body>
</html>

从这个例子是自身宽度算出来的,但是网上查的是 according to the width of the containing box

@yakczh 这个就看你上位之后要爬到哪儿了,达到自身宽度就能上去,至于上去之后还要位移多少,再根据实际情况调整咯~

回到顶部