Skip to content
This page has been auto-translated and may contain errors.View in English

Flexbox ​

你想要三个按钮排成一行,间距均匀,高度一致,无论每个按钮里的文字多少都能垂直居中。放在以前,这意味着浮动、clearfix,还要靠猜。flexbox 正是为这种需求打造的工具:你告诉一个容器把子元素排成一行,然后沿两个轴来回调整,直到它们落在你想要的位置。网页上大部分常见的布局——导航栏、工具栏、一排排卡片——底层其实都是一个 flex 容器。

flex 容器 ​

flexbox 从父元素上的一行代码开始。给一个容器设置 display: flex,它的直接子元素就会并排排成一行。这个容器现在就成了一个**flex 容器**,里面的每个子元素都变成了 flex 项目。

css
.nav {
  display: flex;    /* 子元素现在排成一行 */
}

这些项目会沿着两个方向排布。主轴从左到右延伸,也就是项目排列的方向。交叉轴从上到下延伸,横穿这一行。你后面会沿着这两条轴来调整项目的位置,所以最好现在就在脑子里画出这幅图:主轴是项目所在的那条线,交叉轴是横穿它的方向。

display: flex 把一个元素变成**flex 容器**,它的直接子元素则变成 flex 项目。只有直接子元素参与这个过程,嵌套更深的内容仍由它自己的父元素来排布。默认情况下,项目会从左到右排成一行,每个都收缩到自身内容的宽度。

css
.nav {
  display: flex;    /* 直接子元素变成一行 flex 项目 */
}

整个模型都建立在两条轴上。主轴是项目流动的方向,默认是一行,也就是从左到右。交叉轴与主轴垂直,从上到下延伸。你后面会遇到的每一个对齐属性,都只作用于其中一条轴,所以先把这两条轴分清楚,后面的东西才能自然地对上号。

display: flex 会给元素建立一个**flex 格式化上下文**:它的直接子元素变成 flex 项目,改由 flex 算法来排布,而不再走普通流。只有直接子元素会参与其中,文本节点和更深层的后代元素都在它们各自的范围内处理。这和阻止边距折叠的边界机制是同一类东西,这也是 flex 布局比它取代的那种浮动加 clear 的老做法表现更可预测的原因之一。

css
.nav {
  display: flex;    /* 建立一个 flex 格式化上下文 */
}

一切都由两条轴支配。主轴由 flex-direction 决定(默认是一行,即从行内起始方向到行内结束方向),交叉轴与主轴垂直。这个区分不只是叫法上的:justify-content 始终作用于主轴,align-items 始终作用于交叉轴,所以一旦你把 flex-direction 改成 column,这两个属性的作用方向就会互换。按轴来命名布局,而不是按"水平"和"垂直",正是这一切在方向改变时依然保持一致的原因。

css
.nav {
  display: flex;
}

只需一条声明,任何一个块元素的子元素就不再从上到下堆叠,而是并排排成一行。这一章接下来的内容,都是讲怎么在把这些项目放进容器之后,进一步调整它们的位置。

Junoflex 容器 在父元素上设置 display: flex,它的直接子元素就会排成一行。这个父元素就是 flex 容器,子元素就是 flex 项目。项目沿两个方向移动:主轴是它们所在的那条线,交叉轴横穿主轴,你会沿着这两条轴去调整项目位置。
Junoflex 容器display: flex 会让容器把直接子元素排成一行 flex 项目。只有直接子元素参与,嵌套更深的内容不受影响。脑子里要分清两条轴:主轴是流动方向,交叉轴与它垂直,每个对齐属性都只作用于其中一条。
Junoflex 容器display: flex 会开启一个 flex 格式化上下文,直接子元素改由 flex 算法排布,内部的边距也不再折叠。按轴来命名这个布局,而不是按水平和垂直,因为 justify-content 跟着主轴走,align-items 跟着交叉轴走。把 flex-direction 改成 column,这两个属性的作用方向就会互换。

沿轴对齐 ​

项目排成一行之后,有两个属性可以调整它们的位置。justify-content 沿主轴(也就是项目排列的方向)分配间距。align-items 沿交叉轴移动它们,也就是在这一行里上下调整。

css
.nav {
  display: flex;
  justify-content: space-between;   /* 沿这一行把项目推开 */
  align-items: center;              /* 让它们在垂直方向上居中对齐 */
}

justify-content 常用的值有 flex-start(挤在左边)、center(挤在中间)、flex-end(挤在右边)、space-between(项目之间留空隙,两端不留)、space-around(每个项目两侧都留空隙)。至于 align-items,最常用的就是 center。

要让某个东西在两个方向上都居中,人人都会先学到的一招:把两个属性都设成 center。

css
.hero {
  display: flex;
  justify-content: center;   /* 沿主轴居中 */
  align-items: center;       /* 沿交叉轴居中 */
}

对齐由两个属性负责,一条轴一个。justify-content 沿主轴分配项目,align-items 则把它们定位在交叉轴上。因为它们各管各的轴,所以彼此永不冲突,作用方向是互相垂直的。

css
.nav {
  display: flex;
  justify-content: space-between;   /* 沿主轴铺开项目 */
  align-items: center;              /* 在交叉轴上居中 */
}

justify-content 值得记住的是 flex-start、center、flex-end、space-between(项目之间的间隙相等,两端贴边)和 space-around(每个项目周围的空间相等,所以两端的间隙只有中间的一半)。对 align-items 来说,默认值是 stretch,它会让每个项目都撑满容器的高度,这也是为什么一排卡片即使内容不同,高度也会一致。设置 align-items: center 后,它们就会缩回内容本身的高度,居中排列。

在两条轴上同时居中,正是让 flexbox 出名的那对属性组合:

css
.hero {
  display: flex;
  justify-content: center;   /* 主轴 */
  align-items: center;       /* 交叉轴 */
}

对齐按轴清晰地拆分开。justify-content 沿主轴分配剩余空间,align-items 设定每个项目在交叉轴上的默认位置。它们不会互相冲突,因为作用于互相垂直的两条轴,但两者都依赖于哪个方向是"主轴",所以一旦把 flex-direction 设成 column,它们各自控制的方向就会互换。

css
.nav {
  display: flex;
  justify-content: space-between;   /* 剩余空间分配到项目之间 */
  align-items: center;              /* 每个项目在交叉轴上居中 */
}

容易让人困惑的地方在于:justify-content 只有在主轴上存在剩余空间时才有意义。它分配的是空闲空间,所以如果项目已经填满了容器(或者溢出了容器),space-between 和 center 看起来会一模一样,因为根本没有空间可分配。相比之下,align-items 是在容器的交叉轴尺寸内独立定位每个项目,它的初始值是 stretch:没有固定交叉轴尺寸的项目会撑满容器。等高的卡片行,正是 align-items: stretch 在做它默认该做的事。要针对单个项目覆盖这个行为,用下面尺寸章节会讲到的 align-self。

css
.hero {
  display: flex;
  justify-content: center;   /* 主轴居中 */
  align-items: center;       /* 交叉轴居中,覆盖了默认的 stretch */
}

两个项目,容器上加一个 min-height,两者都设成 center,你就解决了曾经让一代开发者靠绝对定位和负边距硬凹出来的居中难题。

Juno沿轴对齐justify-content 沿主轴分配间距,align-items 沿交叉轴移动项目。第一个属性要记住的值是 center、space-between 和 flex-start。把两个都设成 center,里面的内容就会正好居中,这个操作你会反复用到。
Juno沿轴对齐 一条轴对应一个属性:主轴用 justify-content,交叉轴用 align-items。默认的 align-items: stretch 就是一排卡片能自动等高的原因。两个都设成 center,是值得记住的居中万能招。
Juno沿轴对齐justify-content 分配的是主轴上的剩余空间,如果项目已经填满这一行,它就什么都不做,这解释了很多"为什么居中不生效"的困惑。align-items 在交叉轴上定位每个项目,默认是 stretch,让你白得一排等高的元素。切换 flex-direction 时,记住这两个属性作用的轴也会互换。

方向与换行 ​

默认情况下,flex 容器把项目排成一行。用**flex-direction**可以改变这一点。把它设成 column,项目就会从上到下堆叠,而不是并排排列。

css
.sidebar {
  display: flex;
  flex-direction: column;   /* 项目现在垂直堆叠 */
}

当一行里的项目挤不下时,它们会被越挤越窄。要让它们换到新的一行,而不是被压扁,加上 flex-wrap: wrap。

css
.card-row {
  display: flex;
  flex-wrap: wrap;   /* 放不下的项目会移到下一行 */
}

要设置项目之间的间距,用 gap。它会在每个项目之间加上统一的间隙,而不会在最外侧边缘也加上空间。

css
.card-row {
  display: flex;
  gap: 16px;   /* 每个项目之间留 16px */
}

flex-direction 决定主轴。默认值 row 从左到右延伸;column 会把主轴变成垂直方向,于是项目从上到下堆叠,更重要的是,这时 justify-content 控制的是垂直方向的间距,align-items 控制的是水平方向。如果需要反转顺序,还有 row-reverse 和 column-reverse 可用。

css
.sidebar {
  display: flex;
  flex-direction: column;   /* 主轴现在是垂直方向 */
  justify-content: center;  /* 也就是说这行代码是在垂直居中 */
}

默认情况下,一条 flex 行永远不会断行:放不下的项目会被压缩以适应,而不是换行。设置 flex-wrap: wrap 之后,放不下的项目会移到新的一行,这也是不用媒体查询就能实现响应式卡片网格的关键。

css
.card-row {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;   /* gap 在两条轴上都生效:行间和列间都留 16px */
}

用 gap 来控制间距,而不是给项目加边距。gap 只作用于项目之间,从不加在最外侧边缘,所以你永远不用去掉最后一个子元素多余的边距。

flex-direction 定义主轴,所有依赖轴向的属性都跟着它走。默认值 row 让主轴变成水平方向;column 让主轴变成垂直方向,同时悄悄把 justify-content 改为控制垂直间距,align-items 改为控制水平方向。-reverse 系列的值会翻转主轴的方向,但注意它们只在视觉上重新排序:DOM 顺序不变,所以键盘和屏幕阅读器的顺序依然按源码走——这是使用 row-reverse 时一个实实在在的无障碍问题。

css
.sidebar {
  display: flex;
  flex-direction: column;    /* 主轴变垂直;justify-content 现在做垂直居中 */
  gap: 8px;
}

flex-wrap: wrap 解除了单行限制。不加它,flex 容器会强行把所有项目挤在一行,用压缩的方式分摊不足的空间;加上它,超出主轴尺寸的项目会换到新行,容器就变成了多行。一旦开启换行,align-content(和 align-items 是不同的属性)就负责控制这些行在交叉轴上的间距分配。gap 设置的是项目之间、以及换行后行与行之间的间距,它是正确的选择:不像边距,它不会折叠,也不会在边缘多加空间,读起来就是一个单纯的间距决定。

css
.card-row {
  display: flex;
  flex-wrap: wrap;
  gap: 16px 24px;   /* 行间距 16px,列间距 24px */
}

这也是它和CSS grid分界的地方。flex 的换行是填满一行就断开,所以不同行上的项目不会在列上对齐。当你需要真正的二维网格,行列都要对齐时,那是 grid 的活,不是 flexbox 的。

Juno方向与换行flex-direction: column 让项目垂直堆叠,而不是排成一行。flex-wrap: wrap 让项目在挤不下时换到新的一行,而不是被压扁。gap 在项目之间加上均匀的间距,且不会在边缘多加空间,所以用它比用边距更合适。
Juno方向与换行flex-direction 决定主轴,切换到 column 之后 justify-content 就变成了控制垂直间距,这会让人意外一次。flex-wrap: wrap 是卡片行不用媒体查询就能自动换行的关键。用 gap 来控制间距,省得再去处理最后一个项目上多余的边距。
Juno方向与换行flex-direction 定义主轴,所有依赖轴向的属性都跟着它走,而 -reverse 系列的值只在视觉上重新排序,不会动 DOM 和焦点顺序,所以要小心使用。flex-wrap 让容器变成多行,把行间距交给 align-content 管理。当换行后的项目需要真正对齐成列时,那是 grid 该做的事,不是 flexbox。

灵活的项目尺寸 ​

flexbox 的意义就在于项目可以"弹性"变化,通过增大或缩小来分享空间。你会用得最多的简写是给项目设置 flex: 1,它告诉项目要增长,均分可用的空间。

css
.card {
  flex: 1;   /* 每张卡片都增长,占据这一行均等的份额 */
}

给一行里的三张卡片都设置 flex: 1,它们就会把容器平分成三份等宽的区域。给其中一张卡片设置 flex: 2,它占据的空间就是其他卡片的两倍。就是这一个数字,决定了项目之间如何分配剩余空间。

flex: 1 背后其实是三个独立的控制项:项目增长多少、缩小多少、以及起始尺寸是多少。一开始你很少会逐个去设置它们,这个简写已经覆盖了常见情况,但知道它们的存在会有帮助。

有三个属性决定项目如何分享空间。flex-grow 设置项目要增长多少来填补剩余空间(0 表示不增长)。flex-shrink 设置空间不够时项目要让出多少(默认值 1,表示可以缩小)。flex-basis 设置项目在增长或缩小之前的起始尺寸,默认值是 auto,意思是"使用内容尺寸或宽度"。

flex 这个简写把这三者打包在一起,实际会用到的取值并不多:

css
.card {
  flex: 1;   /* grow 1, shrink 1, basis 0:等宽的列 */
}

.sidebar {
  flex: 0 0 240px;   /* 不增长,不缩小,始终保持 240px */
}

flex: 1 是最常用的一招:无论内容多少,它都能让项目把这一行平分成等份,因为 0 的基础尺寸意味着项目从零开始,只按照各自的增长系数增长。flex: 0 0 240px 则是搭配 flex: 1 主区域使用的固定宽度侧边栏,这种布局你会一遍又一遍地用到。

项目尺寸的计算要经过容器依次评估的三个属性。flex-basis 是项目在任何剩余空间被分配之前的起始主轴尺寸;它的默认值 auto 会让位给项目的 width(或内容尺寸)。接下来容器会计算所有基础尺寸之和与自身尺寸之间的差值。如果有剩余空间,就按每个项目的 flex-grow 比例分配下去。如果空间不足,就按每个项目的 flex-shrink 值(再按其基础尺寸加权)比例收回。

css
/* flex: 1 展开为 flex-grow: 1, flex-shrink: 1, flex-basis: 0% */
.card {
  flex: 1;   /* 基础尺寸为 0,所以项目忽略内容宽度,按增长系数分配空间 */
}

微妙之处在于 flex-basis 和 width 之间的关系,因为它们会相互影响。flex-basis: 0(就像 flex: 1 里那样)会让项目忽略内容,单纯按增长系数分配空间,得到真正等宽的列。flex-basis: auto(就像 flex: auto,或者一个普通的 width)会让每个项目先按内容尺寸或声明的宽度起步,然后才分享剩余空间,所以内容多的项目最终会更宽。在这两者之间做选择,其实就是在决定内容是否应该影响最终的宽度。

再说两条生产环境要注意的点。第一,当 flex-basis 和 width 同时设置时,flex-basis 会胜出,所以一旦一个元素成了 flex 项目,最好通过 flex 来表达它的尺寸。第二,是溢出的陷阱:flex 项目默认的 min-width 是 auto,意味着它不会缩小到比自身内容的固有尺寸更小,所以一个很长的单词或溢出的子元素就可能把整行撑爆。解决办法是在应该允许缩小的项目上设置 min-width: 0。

css
.main {
  flex: 1;
  min-width: 0;   /* 让这个项目可以缩小到比内容更小,长文本就会截断而不是溢出 */
}

align-self 可以对单个项目覆盖 align-items 的设置,所以某一个项目可以停在 flex-start,而其他项目都居中。再说一句工具选择的建议:一维布局——导航栏、工具栏、一行或一列的项目——就用 flexbox。当你需要行和列同时对齐时,CSS grid 才是正确的工具。如果你不确定这背后宽度到底是怎么算出来的,尺寸单位一章讲了百分比等单位在这里的表现。

css
.layout {
  display: flex;
  gap: 24px;
}
.sidebar {
  flex: 0 0 240px;   /* 固定 240px,永不增长也不缩小 */
}
.main {
  flex: 1;           /* 占据所有剩余空间 */
}

一个固定的侧边栏配一个弹性的主内容列,三行代码就表达清楚了意图。这一种模式,加上前面讲过的对齐导航栏,覆盖了你要构建的大部分布局。

Juno灵活的项目尺寸 给项目设置 flex: 1,就是让它增长,均分这一行的空间,所以三张都设了 flex: 1 的卡片会等宽。把其中一个改成 flex: 2,它就会占两倍的空间。就是这一个数字,决定了 flex 项目之间怎么分配空间。
Juno灵活的项目尺寸flex 简写把增长、缩小和基础尺寸打包在一起。flex: 1 能得到等宽的列,因为它的 0 基础尺寸让项目从零开始,均匀增长。flex: 0 0 240px 就是搭配 flex: 1 主区域的固定侧边栏,这种布局你会反复用到。
Juno灵活的项目尺寸flex-basis 是空间分配之前的起始尺寸,它在 flex 项目上会胜过 width,所以 flex: 1(基础尺寸为 0)能均分空间,而 flex: auto 则允许内容影响宽度。留意默认的 min-width: auto,它会阻止项目缩小到比内容更小,导致溢出这一行,直到你设置 min-width: 0。用 align-self 把单个项目从统一对齐中抽出来,一旦需要行列同时对齐,就换成 grid。