技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTCSS

CSS 常见布局方式:Flex 与 Grid

所属馆藏
CSS
文件格式
Markdown
原始路径
CSS/04-CSS 常见布局方式 flex grid
本文目录24 个章节
  1. 传统盒模型布局方式
  2. 什么是 Flex 布局?
  3. 使用 Flex 布局
  4. Flex 容器属性
  5. Flex item 属性
  6. Grid 的浏览器支持
  7. Grid 的基本概念
  8. 使用 Grid 布局
  9. 行或列的最小和最大尺寸
  10. 重复行或列
  11. 网格间距
  12. 通过网格线定位 Grid item
  13. 合并网格行与网格列
  14. 自定义网格线名称
  15. 通过网格区域命名和定位项目
  16. 常用的 CSS 布局:水平和垂直居中
  17. 居中的原理总结
  18. 圣杯布局的 HTML 结构
  19. 圣杯布局的传统实现
  20. 使用 Grid 实现圣杯布局
  21. 双飞翼布局结构
  22. 使用 Flex 或 Grid 替代
  23. 总结
  24. 参考资料

CSS 常见布局方式:Flex 与 Grid

Category(分类): CSS Status: 未知

原文参考:CSS 常见布局方式

本文保留原文关于传统布局、Flex、Grid、居中、圣杯布局和双飞翼布局的主要内容,并修正过时的浏览器兼容性、代码语法、Grid/Flex 概念和居中示例。原文截图主要用于辅助理解,具体浏览器支持情况应以当前 MDN、Can I Use 和目标浏览器测试为准。

CSS 布局方式示意图

传统盒模型布局方式

传统页面布局通常会组合使用以下能力:

  • 普通流(Normal Flow):元素按照文档顺序参与块布局或行内布局;
  • 定位布局(Positioning):通过 positiontoprightbottomleft 等属性调整元素位置;
  • 浮动布局(Float):通过 float 让元素脱离普通流,并让行内内容环绕它。

display 并不只是“文档流布局”的开关,它还可以创建 Flex、Grid、Table、Flow-root 等不同的格式化上下文。

文档流布局

普通流是最基本的布局方式。块级盒通常沿块方向依次排列,行内盒在行盒中从左到右排列,空间不足时换行。

.block {
  display: block;
}

.inline {
  display: inline;
}

浮动布局

浮动最初主要用于图片和文字环绕:

.article-image {
  float: left;
  width: 240px;
  margin: 0 1rem 0.5rem 0;
}

浮动元素脱离普通流中的块布局位置,但仍会影响周围的行内内容。现代页面整体布局通常优先使用 Flexbox 或 Grid。

定位布局

定位适合处理覆盖层、弹窗、角标和相对于容器的精确定位:

.container {
  position: relative;
}

.badge {
  position: absolute;
  top: 0;
  right: 0;
}

不要把绝对定位当作页面整体布局的默认方案,否则容易造成内容重叠、响应式适配困难和文档顺序与视觉顺序不一致。

Flex 布局

Flex 布局示意图

仅仅通过普通流、浮动和定位来实现复杂布局,往往需要较多技巧。Flexbox 可以更直接地处理一维布局、空间分配、对齐和换行。

什么是 Flex 布局?

Flex 是 Flexible Box 的缩写,意为弹性盒布局。它为盒状模型提供了沿主轴和交叉轴分配空间的能力。

Flexbox 规范经历过多个历史版本。现代 Chrome、Edge、Firefox 和 Safari 已经广泛支持 Flexbox;IE10/11 的实现较早且存在兼容性差异,IE 也已经停止维护。新项目通常不需要专门为 IE 添加 Flex 前缀,但维护旧项目时仍应根据目标浏览器测试。

Flex 早期浏览器支持情况,属于历史截图

使用 Flex 布局

将元素的 display 设置为 flex,它就成为 Flex 容器,直接子元素会成为 Flex item:

.flex-container {
  display: flex;
}

.inline-flex-container {
  display: inline-flex;
}

不要把 flexinline-flex 写在同一个示例中,因为后面的声明会覆盖前面的声明。旧浏览器前缀也应根据项目目标单独处理。

设置 Flex 容器后,直接子元素的 floatclearvertical-align 不再按照普通流或普通行内布局的方式起作用。需要对齐 Flex item 时,应使用 Flexbox 自身的对齐属性。

Flex 的容器和轴

Flex 的核心概念是容器和轴:

  • Flex container:设置了 display: flexinline-flex 的元素;
  • Flex item:Flex 容器的直接子元素;
  • 主轴(main axis):项目排列的主要方向;
  • 交叉轴(cross axis):与主轴垂直的方向;
  • main start / main end:主轴的起点和终点;
  • cross start / cross end:交叉轴的起点和终点;
  • main size / cross size:项目在主轴和交叉轴方向占据的尺寸。

主轴默认由 flex-direction: row 决定,通常沿当前书写方向的行方向排列。交叉轴不是永远“顺时针旋转 90°”,它还会受到 directionwriting-mode 和反向排列值的影响。

Flex 容器、主轴和交叉轴

Flex 容器属性

常用的 Flex 容器属性包括:

  • flex-direction:主轴方向;
  • flex-wrap:是否换行;
  • flex-flow:前两个属性的简写;
  • justify-content:主轴上的对齐和剩余空间分配;
  • align-items:单行在交叉轴上的项目对齐;
  • align-content:多行 Flex line 在交叉轴上的整体对齐。

flex-direction

flex-direction 决定主轴方向:

.flex-container {
  flex-direction: row; /* 默认,沿行方向排列 */
}

.column-container {
  flex-direction: column; /* 沿块方向排列 */
}

可选值:

  • row:主轴沿行方向;
  • row-reverse:沿行方向反向排列;
  • column:主轴沿块方向;
  • column-reverse:沿块方向反向排列。

flex-direction 属性示意图

flex-wrap

flex-wrap 决定 Flex item 在一条 Flex line 放不下时是否换行:

.flex-container {
  flex-wrap: nowrap; /* 默认,不换行 */
}

.wrapped-container {
  flex-wrap: wrap; /* 换行,第一行位于交叉轴起点 */
}

.reverse-wrapped-container {
  flex-wrap: wrap-reverse; /* 换行,Flex line 反向排列 */
}

flex-wrap 属性示意图

wrap-reverse 的实际视觉方向还会受到书写模式和交叉轴方向影响,不应简单固定描述为“第一行在下方”。

justify-content

justify-content 定义项目在主轴上的对齐和剩余空间分配:

.flex-container {
  justify-content: flex-start; /* 起点对齐 */
}

.center-container {
  justify-content: center;
}

.space-between-container {
  justify-content: space-between;
}

.space-around-container {
  justify-content: space-around;
}

.space-evenly-container {
  justify-content: space-evenly;
}

常见值:

  • flex-start:靠主轴起点;
  • flex-end:靠主轴终点;
  • center:主轴居中;
  • space-between:两端贴边,项目间均匀分配;
  • space-around:每个项目两侧空间相等,边缘空间是项目间空间的一半;
  • space-evenly:项目之间以及项目与容器边缘之间的空间都相等。

justify-content 属性示意图

flex-flow

flex-flowflex-directionflex-wrap 的简写,初始值为 row nowrap

.flex-container {
  flex-flow: row wrap;
}

align-items

align-items 定义单条 Flex line 中项目在交叉轴上的对齐方式:

.flex-container {
  align-items: stretch; /* 初始值,满足条件时拉伸项目 */
}

.center-items {
  align-items: center;
}

.baseline-items {
  align-items: baseline;
}

常见值:

  • flex-start:交叉轴起点;
  • flex-end:交叉轴终点;
  • center:交叉轴居中;
  • baseline:按项目的基线对齐;
  • stretch:项目交叉轴尺寸为 auto 且条件满足时进行拉伸。

align-items 属性示意图

align-content

align-content 定义多条 Flex line 在交叉轴上的整体对齐方式。单行 Flex 容器通常看不到它的效果,必须设置换行并且交叉轴上有剩余空间:

.flex-container {
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
}

.center-lines {
  align-content: center;
}

常见值还包括 flex-endspace-betweenspace-aroundspace-evenlystretch

align-content 属性示意图

Flex item 属性

Flex item 常用属性包括:

  • order:项目的视觉排列顺序;
  • flex-grow:有剩余空间时的增长比例;
  • flex-shrink:空间不足时的收缩比例;
  • flex-basis:参与伸缩前的基准尺寸;
  • flex:前三个属性的简写;
  • align-self:单独覆盖某个项目的交叉轴对齐方式。

order

order 越小,项目通常越靠前,初始值为 0

.first {
  order: -1;
}

.last {
  order: 2;
}

视觉顺序改变不会改变 DOM 顺序。涉及阅读顺序、键盘操作和屏幕阅读器时,应谨慎使用 order

order 属性示意图

flex-grow

flex-grow 定义项目在有剩余空间时的增长比例,初始值为 0

.item {
  flex-grow: 1;
}

如果多个项目的 flex-grow 分别为 12,剩余空间会按比例分配,但实际结果仍会受到最小尺寸、最大尺寸和基准尺寸影响。

flex-grow 属性示意图

flex-shrink

flex-shrink 定义空间不足时项目的收缩比例,初始值为 1

.item {
  flex-shrink: 1;
}

它不要求 flex-wrap 必须是 nowrap。当 Flex 容器换行时,项目会先按照换行规则分到不同的 Flex line,再在各自的 line 内进行空间分配。

flex-shrink 属性示意图

flex-basis

flex-basis 定义项目参与伸缩前的基准尺寸。主轴为行方向时,它通常对应宽度;主轴为列方向时,它通常对应高度:

.item {
  flex-basis: 200px;
}

.auto-item {
  flex-basis: auto;
}

auto 会参考项目的主轴尺寸属性和内容尺寸,具体计算还会受到 min-widthmin-height 等因素影响。

flex-basis 属性示意图

flex

flexflex-growflex-shrinkflex-basis 的简写:

.item {
  flex: 1 1 200px;
}

其初始值是:

flex: 0 1 auto;

常用快捷值:

.item-auto {
  flex: auto; /* 1 1 auto */
}

.item-none {
  flex: none; /* 0 0 auto */
}

.item-fill {
  flex: 1; /* 通常按 1 1 0% 处理 */
}

flex-basis 与 flex 简写示意图

align-self

align-self 允许单个项目覆盖父容器的 align-items

.item {
  align-self: auto;
}

.center-item {
  align-self: center;
}

.start-item {
  align-self: flex-start;
}

auto 会使用父容器的 align-items。这个属性不会改变项目在主轴上的位置。

align-self 属性示意图

Grid 网格布局

Flexbox 主要解决一维布局问题:一次处理一条主轴。Grid 是二维布局系统,可以同时定义行和列,更适合页面骨架、卡片网格和复杂的二维区域布局。

Grid 与 HTML 表格布局都具有行列结构,但 Grid 通过 CSS 定义,不需要使用 <table><tr><td> 等特定 HTML 标签,也不会把数据语义伪装成表格。

Grid 可以在不改变 DOM 结构的情况下调整视觉布局,但不代表可以忽略语义、阅读顺序和可访问性。视觉重排时仍应保证源代码顺序合理。

Grid 的浏览器支持

Grid 浏览器支持历史截图

现代 Chrome、Edge、Firefox 和 Safari 已经广泛支持 CSS Grid。原文的兼容性截图来自较早时期,只能作为历史资料;如果需要兼容 IE,Grid 支持不足,通常需要降级方案或其他布局实现。

Grid 的基本概念

网格线(Grid Line)

网格线是网格中行和列的分界线。每条轨道的两侧都有网格线,可以使用数字或自定义名称引用:

网格线示意图

网格轨道(Grid Track)

相邻两条网格线之间的空间称为网格轨道。列轨道控制列方向尺寸,行轨道控制行方向尺寸:

网格轨道示意图

网格单元格(Grid Cell)

由相邻两条行线和两条列线围成的最小区域称为网格单元格:

网格单元格示意图

网格区域(Grid Area)

由任意四条网格线围成的区域称为网格区域。一个区域可以包含一个或多个网格单元格,类似表格中的合并单元格:

网格区域示意图

使用 Grid 布局

将元素的 display 设置为 gridinline-grid,它就成为 Grid 容器,直接子元素会成为 Grid item:

.grid-container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: auto;
}

subgrid 不是 display 的值,而是可以用于 Grid item 的轨道定义:

.child-grid {
  display: grid;
  grid-template-columns: subgrid;
}

使用 subgrid 时,项目必须处于合适的父级 Grid 轨道环境中。

对于 Grid item,floatclearvertical-align 不会按照普通流、浮动或行内布局的方式控制其位置,应使用 grid-areaplace-itemsalign-selfjustify-self 等 Grid 属性。

不设置 grid-template-columns

如果不设置显式列轨道,在简单的自动放置场景下,Grid item 通常会按一列排列,并生成隐式轨道:

<div class="grid-container">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item">3</div>
  <div class="item">4</div>
  <div class="item">5</div>
  <div class="item">6</div>
</div>
.grid-container {
  display: grid;
  grid-template-rows: 50px 80px 100px;
  background: pink;
}

.item {
  border: 2px solid palegoldenrod;
  color: white;
  font-size: 20px;
  text-align: center;
}

不设置 grid-template-columns 的效果

如果存在显式定位或其他自动放置规则,也可能生成隐式列,不能简单地认为永远只有一列。

设置列和行

grid-template-columns 定义显式列轨道,grid-template-rows 定义显式行轨道:

.grid-container {
  display: grid;
  grid-template-columns: 50px 40px 100px 80px;
  grid-template-rows: 50px 100px 60px 80px;
  padding: 20px;
  background: pink;
}

设置了 4 列和 4 行时,即使只有 6 个项目,未被项目占用的显式轨道仍然存在,只是可能没有可见内容。不能说“后面的轨道值无效”。

设置 grid-template-columns 和 grid-template-rows

fr 单位

fr 是 Grid 的弹性轨道单位,用于在扣除固定尺寸轨道、间距和内边距后分配剩余空间:

.grid-container {
  display: grid;
  grid-template-columns: 1fr 1fr 2fr;
  grid-template-rows: 50px 100px 60px;
  padding: 20px;
  background: pink;
}

上例中剩余空间大致按 1:1:2 分配,但内容最小尺寸、min-width、轨道最小值和最大值仍可能影响最终结果。

使用 fr 按比例设置网格列

行或列的最小和最大尺寸

minmax(min, max) 可以定义网格轨道的最小值和最大值:

.grid-container {
  display: grid;
  grid-template-columns: 1fr 1fr 2fr;
  grid-template-rows: minmax(100px, 1fr) minmax(50px, 1fr);
  min-height: 300px;
  padding: 20px;
  background: pink;
}

minmax() 不会简单地把剩余空间按照某个固定公式平均分配。实际轨道尺寸还会受到内容、容器尺寸、对齐方式、间距和其他轨道的影响。

如果使用固定最大值:

grid-template-rows: minmax(100px, 200px) minmax(50px, 200px);

浏览器会按照 Grid Track Sizing Algorithm 计算轨道,不能直接套用“总高度减去最小值再除以 2”的推导。

minmax 设置最小和最大尺寸

重复行或列

repeat() 是一个函数,用于重复生成网格轨道:

.grid-container {
  display: grid;
  grid-template-columns: repeat(2, 100px);
  grid-template-rows: repeat(3, 100px);
  gap: 15px 50px;
  padding: 20px;
  background: pink;
}

repeat 函数

还可以配合自动填充创建响应式网格:

.grid-container {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 1rem;
}

网格间距

现代写法使用 gaprow-gapcolumn-gap

.grid-container {
  display: grid;
  grid-template-columns: repeat(2, 100px);
  grid-template-rows: repeat(3, 100px);
  column-gap: 50px;
  row-gap: 15px;
  background: pink;
}

grid-column-gapgrid-row-gapgrid-gap 是旧代码中常见的写法,现代项目推荐使用无布局模式专属前缀的 gap 系列属性。

设置 Grid 间距

通过网格线定位 Grid item

Grid item 可以使用网格线编号定位:

<div class="grid-container">
  <div class="item item1">1</div>
  <div class="item item2">2</div>
  <div class="item item3">3</div>
  <div class="item item4">4</div>
  <div class="item item5">5</div>
  <div class="item item6">6</div>
</div>
.grid-container {
  display: grid;
  grid-template-columns: repeat(2, 100px);
  grid-template-rows: repeat(3, 100px);
  column-gap: 50px;
  row-gap: 15px;
  padding: 20px;
  background: pink;
}

.item {
  border: 2px solid palegoldenrod;
  color: white;
  font-size: 20px;
  text-align: center;
}

.item1 {
  grid-row-start: 2;
  grid-row-end: 3;
  grid-column-start: 2;
  grid-column-end: 3;
  color: #000;
  background: #fffa90;
}

通过网格线定位 Grid item

grid-rowgrid-row-startgrid-row-end 的简写,grid-columngrid-column-startgrid-column-end 的简写:

.item {
  grid-row: 2 / 3;
  grid-column: 3 / 4;
}

只提供一个值时,第二个值通常为 auto,不是同时设置起点和终点:

.item {
  grid-row: 2;       /* row-start: 2,row-end: auto */
  grid-column: 3 / 4;
}

grid-area 的四个值依次是:

grid-area: row-start / column-start / row-end / column-end;

例如:

.item {
  grid-area: 2 / 2 / 3 / 3;
}

合并网格行与网格列

可以使用起止网格线让一个项目占据多个轨道:

.item1 {
  grid-column-start: 1;
  grid-column-end: 3;
  grid-row-start: 2;
  grid-row-end: 4;
}

合并网格行与网格列

也可以使用 span 表示跨越的轨道数量:

.item1 {
  grid-row: 2 / span 3;
  grid-column: span 2;
}

grid-column: span 2 表示从自动确定的列起点开始跨越两列,不代表固定的列编号。

自定义网格线名称

Grid 线可以在轨道定义中设置名称,名称必须使用方括号:

.grid-container {
  display: grid;
  grid-template-columns:
    [col1-start] 100px
    [col1-end col2-start] 100px
    [col2-end] 100px;
  grid-template-rows:
    [row1-start] 100px
    [row1-end row2-start] 100px
    [row2-end] 100px;
  gap: 5px;
}

.a {
  grid-column: col1-start / col2-end;
  grid-row: row1-start;
}

自定义网格线名称

线名称可以提高复杂布局的可读性,但名称要保持稳定,避免与项目中的业务命名混淆。

通过网格区域命名和定位项目

什么是网格区域?

网格区域(Grid Area)是由四条网格线围成的逻辑空间,可以包含一个或多个网格单元格。它可以通过网格线编号定义,也可以使用 grid-template-areas 命名。

使用 grid-template-areas

. 表示空的网格单元格,同一个区域名称可以跨越多个单元格,但必须组成矩形,不能组成 L 形或其他不规则形状:

<div class="layout">
  <header class="header">header</header>
  <main class="content">content</main>
  <aside class="sidebar">sidebar</aside>
  <footer class="footer">footer</footer>
</div>
.layout {
  display: grid;
  grid-template-columns: 200px repeat(4, 1fr);
  grid-template-rows: 50px 150px 50px;
  grid-template-areas:
    'header header header header header'
    'sidebar content content content content'
    'footer footer footer footer footer';
  gap: 5px;
  text-align: center;
  background: pink;
}

.header {
  grid-area: header;
  background: white;
}

.content {
  grid-area: content;
  background: #fffa90;
}

.sidebar {
  grid-area: sidebar;
  background: #5bc0de;
}

.footer {
  grid-area: footer;
  background: #ffff00;
}

原文的区域模板每行有 5 个单元格,却只定义了 3 列,已修正为 5 列,使区域模板和轨道数量一致。

使用 grid-template-areas 定义区域

同一个 Grid 行中的项目默认会拉伸到该行的交叉轴尺寸,因此内容较少的 sidebar 可能与 content 保持同一行高。但这不是因为两个元素内容高度自动相等,而是因为 Grid 项目的默认对齐方式通常为 stretch。设置 align-items: start 后,项目就不会自动拉伸。

常用的 CSS 布局:水平和垂直居中

居中方式不需要死记很多种写法,关键是判断:

  • 子元素是固定尺寸还是内容尺寸;
  • 需要水平居中、垂直居中还是同时居中;
  • 是否允许使用 Flexbox 或 Grid;
  • 是否需要兼容非常旧的浏览器;
  • 元素是块级盒、行内盒还是行内块盒。

现代项目中,优先推荐 Flexbox 或 Grid。

固定尺寸元素的绝对定位居中

.container {
  position: relative;
  width: 300px;
  height: 200px;
  background: pink;
}

.inner {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100px;
  height: 50px;
  background: white;
  text-align: center;
  transform: translate(-50%, -50%);
}

这种方法适合元素尺寸固定或允许覆盖其他内容的场景。transform 的现代浏览器支持已经很好,不需要把 transformXtransformY 当作 CSS 属性;正确写法是 transform: translate(...)

固定宽度元素的水平居中

普通流中的块级元素可以使用:

.inner {
  width: 100px;
  margin-inline: auto;
}

margin: 0 auto 只在适合的块级布局条件下实现水平居中,不能用于所有定位、浮动或行内元素。

宽度未知的行内级元素

父元素的 text-align: center 可以使行内级或行内块子元素水平居中:

.container {
  text-align: center;
}

.inner {
  display: inline-block;
}

它只影响行内级内容,不会自动使普通块级子元素居中。

多个子元素水平居中

.container {
  display: flex;
  justify-content: center;
  gap: 10px;
}

多个子元素水平居中

使用 inline-block 也可以配合 text-align: center,但元素之间可能受源码空白影响;margin: 0 auto 对行内块元素通常不是主要的居中机制。

单行文本垂直居中

旧项目中可以让父容器的 line-height 等于高度,实现单行文字的近似垂直居中:

.container {
  height: 200px;
  line-height: 200px;
}

这种方法只适合单行文本。多行文本使用同样的 line-height 会导致行距过大,不应使用。

多行内容垂直居中:Flexbox

.container {
  display: flex;
  min-height: 400px;
  align-items: center;
  justify-content: center;
}

多行内容垂直居中

多行内容垂直居中:Table layout

为了兼容旧项目,可以使用表格布局:

.container {
  display: table;
  width: 200px;
  height: 400px;
}

.inner {
  display: table-cell;
  vertical-align: middle;
}

vertical-align: middle 应该设置在 table-cell 上,而不是普通的 table 容器上。这个方案会改变布局模型,新项目通常优先使用 Flexbox。

原文还介绍了通过空的行内块元素占满高度的旧技巧。这种方法依赖行盒、基线和源码空白,代码复杂且容易受到内容宽度影响,不建议作为新项目的首选。

图片和文字垂直对齐

如果只是让同一行中的图片和文字相互对齐,可以使用 vertical-align: middle

<div class="media">
  <img src="example.jpg" alt="示例图片">
  <span>图片旁边的文字</span>
</div>
.media {
  font-size: 0;
}

.media img,
.media span {
  display: inline-block;
  vertical-align: middle;
  font-size: 1rem;
}

vertical-align: middle 主要调整行内级元素相对于行盒的对齐,不等同于让整个组合在任意父容器中垂直居中。需要整体居中时,建议使用:

.media {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

图片和文字垂直对齐

居中的原理总结

  • text-align: center 主要作用于行内级内容和行内块子元素;
  • vertical-align 主要作用于行内级盒、行内块盒和表格单元格;
  • line-height 等于容器高度只适合单行文本;
  • 固定尺寸元素可以使用绝对定位和 transform
  • 宽度未知的行内级元素可以使用 text-align
  • 多行内容和未知尺寸内容优先使用 Flexbox 或 Grid;
  • position: relative 的拼写要正确,绝对定位子元素需要相对定位包含块时才设置它;
  • 垂直居中不能只依赖一个通用属性,必须结合元素的布局上下文判断。

圣杯布局(Holy Grail Layout)

圣杯布局是经典的三栏布局:左右两栏固定宽度,中间栏自适应,并且中间内容通常在 DOM 中优先出现。

圣杯布局示意图

圣杯布局的历史实现依赖浮动、负外边距、父容器内边距和相对定位。现代项目通常使用 Grid 或 Flexbox,但了解它有助于维护旧代码和理解传统 CSS 布局。

圣杯布局的 HTML 结构

<div class="holy-grail">
  <main class="middle">中间内容</main>
  <aside class="left">left</aside>
  <aside class="right">right</aside>
</div>

中间栏放在前面,是因为这种历史浮动方案依赖它先占据整行,再通过负外边距把左右栏移动到对应位置。这里的“先放置”是布局算法要求,不是为了让浏览器优先渲染。实际项目仍应优先考虑语义顺序和无障碍阅读顺序。

圣杯布局的传统实现

.holy-grail {
  min-width: 600px;
  padding: 0 200px;
  box-sizing: border-box;
}

.holy-grail::after {
  display: table;
  clear: both;
  content: '';
}

.middle,
.left,
.right {
  float: left;
  min-height: 200px;
  box-sizing: border-box;
}

.middle {
  width: 100%;
  background: paleturquoise;
}

.left {
  width: 200px;
  margin-left: -100%;
  background: palevioletred;
  color: white;
}

.right {
  width: 200px;
  margin-left: -200px;
  background: purple;
  color: white;
}

圣杯布局初始效果

这段代码需要通过父容器左右内边距给固定栏预留空间,并通过负外边距让左右栏移动到中间栏两侧。传统实现还常使用相对定位微调左右栏:

.left {
  position: relative;
  left: -200px;
}

.right {
  position: relative;
  right: -200px;
}

圣杯布局移动左右栏

圣杯布局中间栏预留空间

圣杯布局的主要问题是:当容器宽度小于左右栏总宽度时,中间栏可能过窄、内容重叠或出现横向滚动。因此需要设置合理的最小宽度,或者在小屏幕断点改为单列布局。

圣杯布局最终效果

使用 Grid 实现圣杯布局

现代写法更简单:

.holy-grail {
  display: grid;
  grid-template-columns: 200px minmax(0, 1fr) 200px;
  min-height: 200px;
}

.middle {
  grid-column: 2;
  background: paleturquoise;
}

.left {
  grid-column: 1;
  grid-row: 1;
  background: palevioletred;
}

.right {
  grid-column: 3;
  grid-row: 1;
  background: purple;
}

在窄屏幕上可以直接调整为:

@media (max-width: 640px) {
  .holy-grail {
    grid-template-columns: 1fr;
  }

  .left,
  .middle,
  .right {
    grid-column: 1;
    grid-row: auto;
  }
}

双飞翼布局(Double-wing Layout)

双飞翼布局与圣杯布局都使用“中间栏在前、三栏浮动、左右栏使用负外边距”的思路。区别在于:

  • 圣杯布局:通过父容器左右内边距,并使用左右栏的相对定位避免遮挡;
  • 双飞翼布局:在中间浮动栏内部增加一个内容容器,通过内容容器的左右外边距为固定栏预留空间。

双飞翼布局结构

<div class="double-wing">
  <div class="middle-container">
    <div class="middle-content">中间内容</div>
  </div>
  <aside class="left">left</aside>
  <aside class="right">right</aside>
</div>
.double-wing {
  min-width: 600px;
}

.double-wing::after {
  display: table;
  clear: both;
  content: '';
}

.middle-container,
.left,
.right {
  float: left;
  min-height: 200px;
  box-sizing: border-box;
}

.middle-container {
  width: 100%;
  background: paleturquoise;
}

.middle-content {
  margin: 0 200px;
}

.left {
  width: 200px;
  margin-left: -100%;
  background: palevioletred;
  color: white;
}

.right {
  width: 200px;
  margin-left: -200px;
  background: purple;
  color: white;
}

双飞翼布局示意图

双飞翼布局也不是无限响应式的。当容器宽度小于左右栏总宽度时,仍然会出现溢出或重叠,因此需要设置最小宽度或在小屏幕下切换布局。

双飞翼布局在窄屏下的问题

使用 Flex 或 Grid 替代

三栏布局使用 Flexbox 可以写成:

.layout {
  display: flex;
}

.layout__left,
.layout__right {
  flex: 0 0 200px;
}

.layout__middle {
  flex: 1 1 auto;
  min-width: 0;
}

使用 Grid 可以写成:

.layout {
  display: grid;
  grid-template-columns: 200px minmax(0, 1fr) 200px;
}

这两种方案不需要负外边距、浮动和清除浮动,更容易适配响应式断点。

提问与思考

原文最后提出了一个问题:对于左侧头像和下方文字垂直居中的布局,应该使用什么方法?

头像与文字布局示意图

现代项目可以直接使用 Flexbox:

.profile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}

如果头像和文字需要横向排列:

.profile {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

如果必须兼容 IE8 这样的旧环境,Flexbox 和 Grid 都不可用,只能使用表格布局、行内块加 vertical-align、绝对定位或传统浮动技巧。但这些方案应当被视为历史兼容方案,而不是现代项目的首选。

总结

  1. 普通流、浮动和定位是传统布局能力,页面整体布局不应只依赖浮动和绝对定位;
  2. Flexbox 主要解决一维布局,Grid 主要解决二维布局;
  3. flex-shrink 的初始值是 1,不要求 flex-wrap: nowrap 才生效;
  4. align-content 主要影响多行 Flex line,单行时通常没有明显效果;
  5. subgrid 不是 display 值,而是 Grid 轨道定义中的关键字;
  6. Grid 的行、列、轨道、单元格和区域需要区分;
  7. grid-template-rowsgrid-template-columns 不要混写;
  8. minmax() 的实际结果不能简单按照剩余空间平均推导;
  9. grid-template-areas 的每一行必须有相同数量的单元格,区域名称应组成矩形;
  10. text-alignvertical-alignline-height 有各自适用的布局上下文,不是通用居中属性;
  11. 圣杯布局和双飞翼布局适合学习传统浮动,但现代项目应优先使用 Flexbox 或 Grid;
  12. 视觉重排不能破坏 DOM 顺序、语义和无障碍体验。

参考资料

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS