前端知库

怎样实现三栏布局?圣杯布局和双飞翼布局是什么?

三栏布局是左右两栏固定宽度、中间自适应,而且中间内容要先在 HTML 里出现、先渲染。现在用 Flex 或 Grid 几行就能写;圣杯布局和双飞翼布局是 Flex 普及前的浮动方案,都靠负 margin 把左右栏拉回中间栏两边,区别在于圣杯用父元素的 padding 留出位置,双飞翼在中间栏里套一层用 margin 留出位置。

参考答案

简答

后台页面常见这样的结构:左边是 200px 的菜单,右边是 150px 的辅助栏,中间放主要内容,宽度随窗口变化。这就是三栏布局。面试里通常还会加一个要求:中间栏在 HTML 里写在最前面,让主要内容最先加载和渲染。

现在最简单的写法是 Flex:父元素 display: flex,左右栏设固定宽度并加上 flex-shrink: 0 防止被压缩,中间栏写 flex: 1 占满剩余空间。中间栏写在 HTML 最前面也没关系,给左栏设 order: -1 就能把它显示在前面。Grid 更直接,grid-template-columns: 200px 1fr 150px,再用 grid-area 或 order 摆放位置。

圣杯布局和双飞翼布局是 Flex 还不能用时的方案,三栏都用浮动。它们的共同点是:中间栏写在最前面并设宽度 100%,左右栏用负的 margin-left 把自己拉回到中间栏所在的那一行。区别在于怎样不让中间内容被左右栏盖住:圣杯布局给父元素加左右 padding,再用相对定位把左右栏挪进 padding 里;双飞翼布局在中间栏里再套一层,给这层加左右 margin。

要注意,新项目直接用 Flex 或 Grid。面试问圣杯和双飞翼,主要是考负 margin 和浮动的理解,能讲清楚原理就够了。

Flex 和 Grid 的写法

HTML 里中间栏写在最前面:

HTML
<div class="layout">
  <main class="center">主要内容</main>
  <aside class="left">左栏</aside>
  <aside class="right">右栏</aside>
</div>
CSS
.layout {
  display: flex;
}
.center {
  flex: 1;
  min-width: 0;
}
.left {
  order: -1;
  flex: 0 0 200px;
}
.right {
  flex: 0 0 150px;
}

/* Grid 写法 */
.layout-grid {
  display: grid;
  grid-template-columns: 200px 1fr 150px;
}
.layout-grid .left { grid-column: 1; grid-row: 1; }
.layout-grid .center { grid-column: 2; grid-row: 1; }
.layout-grid .right { grid-column: 3; }

中间栏为什么要加 min-width: 0?Flex 子项默认的最小宽度是内容的宽度,中间如果有很长的不换行文字或宽表格,它会撑开中间栏,把右栏挤出去。设成 0 以后,中间栏才能真正缩小,里面的内容自己滚动或换行。

order: -1 只改变显示顺序,不改变 HTML 顺序,读屏器和 Tab 键还是按 HTML 顺序走。这里中间内容在前,正好是希望的阅读顺序。

圣杯布局怎样用负 margin

CSS
.holy-grail {
  padding: 0 150px 0 200px;
}
.holy-grail > * {
  float: left;
  position: relative;
}
.holy-grail .center { width: 100%; }
.holy-grail .left {
  width: 200px;
  margin-left: -100%;
  left: -200px;
}
.holy-grail .right {
  width: 150px;
  margin-right: -150px;
}

三栏都浮动后,中间栏宽度 100%,占满一整行,左右栏被挤到了第二行。左栏的 margin-left: -100%,这里的百分比相对父元素内容区宽度,相当于往左挪了一整行的宽度,回到第一行的最左边,和中间栏的开头重叠。再用 left: -200px 把它移到父元素左侧的 padding 里。右栏的 margin-right: -150px 让它不再占据宽度,于是浮到中间栏右边,正好落在右侧 padding 里。

双飞翼把父元素的 padding 去掉,改成在中间栏里套一个 <div class="inner">,给它 margin: 0 150px 0 200px。左栏 margin-left: -100%,右栏 margin-left: -150px,不需要相对定位。它比圣杯少一步定位,但多一层 DOM。

常见追问

为什么要让中间栏写在 HTML 最前面?

浏览器按 HTML 顺序解析和渲染,主要内容在前,网络慢时用户能最先看到它。现在页面普遍很快,这个理由没有以前那么重要,但对读屏器和搜索引擎来说,主要内容在前仍然是好的结构。

两栏布局,左边固定、右边自适应有几种写法?

Flex 写法是右边 flex: 1;Grid 写法是 grid-template-columns: 200px 1fr;老写法是左边浮动,右边 margin-left: 200px,或者右边触发 BFC,比如 overflow: hidden,它就不会和浮动元素重叠,见《BFC 解决哪些布局问题,什么时候用 flow-root?》。