布局

弹性盒子是一种用于按行或按列布局元素的一维布局方法。元素可以膨胀以填充额外的空间,收缩以适应更小的空间。本文将…

flex布局

弹性盒子是一种用于按行或按列布局元素的一维布局方法。元素可以膨胀以填充额外的空间,收缩以适应更小的空间。本文将解释所有的基本原理。

方便且灵活的实现的: 在父内容里面垂直居中一个块内容。 使容器的所有子项占用等量的可用宽度/高度,而不管有多少宽度/高度可用。 使多列布局中的所有列采用相同的高度,即使它们包含的内容量不同。

初次使用

设置父容器布局为flex

image-20250420170407764

html
<div class="wrapper">
    <div class="box1" style="background-color: brown;">One</div>
    <div class="box2" style="background-color: beige;">Two</div>
    <div class="box3" style="background-color: blue;">Three</div>
    <div class="box4" style="background-color: antiquewhite;">Four</div>
</div>

原本子容器应该是一行显示一个,但是现在他们在一行内显示,而且宽度也有内容决定

我们的多列布局具有大小相等的列,并且列的高度都是一样。这是因为这样的 flex 项(flex 容器的子项)的默认值是可以解决这些的常见问题的。

image-20250420170641351

当元素表现为 flex 框时,它们沿着两个轴来布局:

在从左到右的语言中,三个 flex 项并排放置在 flex 容器中。主轴——弹性容器布置 flex 方向上的轴——是水平的。主轴的两端是开始端和结束端,分别位于左侧和右侧。交叉轴是垂直的;垂直于主轴。交叉轴的开始端和结束端分别位于顶部和底部。flex 项沿着主轴排列,在这种情况下,宽度称为主轴尺寸,flex 项沿交叉轴排列,在这种情况下,高度称为交叉尺寸。

主轴(main axis)是沿着 flex 元素放置的方向延伸的轴(比如页面上的横向的行、纵向的列)。该轴的开始和结束被称为 main startmain end

交叉轴(cross axis)是垂直于 flex 元素放置方向的轴。该轴的开始和结束被称为 cross startcross end

设置了 display: flex 的父元素被称之为 flex 容器(flex container)。

在 flex 容器中表现为弹性的盒子的元素被称之为 flex 项flex item

列还是行?

弹性盒子提供了 flex-direction 这样一个属性,它可以指定主轴的方向(弹性盒子子类放置的地方)——它默认值是 row,这使得它们在按你浏览器的默认语言方向排成一排

你还可以使用 row-reversecolumn-reverse 值反向排列 flex 项。用这些值试试看吧!

也就是决定主轴的方向

换行

当你在布局中使用定宽或者定高的时候,可能会出现问题即处于容器中的弹性盒子子元素会溢出,破坏了布局。

image-20250420171112824

image-20250420172409174

简单弹性盒子示例,将所有 flex 项都布置在弹性容器的一行中。第八个 flex 项在浏览器窗口溢出,页面具有可见的水平和垂直滚动条,因为它无法容纳在窗口的宽度内,因为前七个 flex 项占用了视口中的可用空间。默认情况下,如果将弹性方向设置为行,浏览器会尝试将所有 flex 项放在一行中;如果弹性方向设置为列,则会尝试将所有 flex 项置于单列中。

解决此问题的一种方法是将以下声明添加到父元素规则中:

flex-wrap: wrap;

image-20250420171251915

同时,把以下规则也添加到父元素规则中:

flex: 200px;

现在尝试一下吧;你会看到布局比原来好多了:

flex 项在弹性容器中分多行排列。flex-wrap 属性在弹性容器中设置为“wrap”,如果上一行中的 flex 项在弹性容器外溢出,则弹性容器中显示新行中的 flex 项。每个 flex 项的宽度为 200 像素。所有物品都被拉伸到相同的高度,与内容最多的 flex 项一样高。

image-20250420172856941

我们现在有多个行。每行都安装了尽可能多的 flex 项。任何溢出都会向下移动到下一行。在 article 上设置的 flex:200px 声明意味着每个声明将至少为 200px 宽。我们稍后会更详细地讨论这个属性。你可能还注意到,最后一行上的最后几个项每个都变得更宽,以便把整个行填满。

缩写flex-flow

到这里,应当注意到存在着 flex-directionflex-wrap——的缩写 flex-flow。比如,你可以将

css
flex-direction: row;
flex-wrap: wrap;

替换为

css
flex-flow: row wrap;

flex 项的动态尺寸

css
article {
  flex: 1;
}

这是一个无单位的比例值,表示每个 flex 项沿主轴的可用空间大小。本例中,我们设置内部元素元素的 flex 值为 1,这表示每个元素占用空间都是相等的,占用的空间是在设置 padding 和 margin 之后剩余的空间。

你还可以指定 flex 的最小值。尝试修改现有的 article 规则:

css
article {
  flex: 1 200px;
}

article:nth-of-type(3) {
  flex: 2 200px;
}

这表示“每个 flex 项将首先给出 200px 的可用空间,然后,剩余的可用空间将根据分配的比例共享”。尝试刷新,你会看到分配空间的差别。

flex 是一个可以指定最多三个不同值的缩写属性

  • 第一个就是上面所讨论过的无单位比例。可以单独指定全写 flex-grow 属性的值。
  • 第二个无单位比例——flex-shrink——一般用于溢出容器的 flex 项。这指定了从每个 flex 项中取出多少溢出量以阻止它们溢出它们的容器。这是一个相当高级的弹性盒子功能,我们不会在本文中进一步说明。
  • 第三个是上面讨论的最小值。可以单独指定全写 flex-basis 属性的值。

我们建议不要使用全写属性,除非你真的需要(比如要去覆盖之前写的)。使用全写会多写很多的代码,它们也可能有点让人困惑。

flex-shrink 属性指定了 flex 元素的收缩规则。flex 元素仅在默认宽度之和大于容器的时候才会发生收缩,其收缩的大小是依据 flex-shrink 的值。

image-20250925012406636

html
<p>the width of content is 500px, flex-basic of flex item is 120px.</p>
<p>A, B, C are flex-shrink:1. D and E are flex-shrink:2</p>
<p>the width of D is not the same as A's</p>
<div id="content">
  <div class="box" style="background-color:red;">A</div>
  <div class="box" style="background-color:lightblue;">B</div>
  <div class="box" style="background-color:yellow;">C</div>
  <div class="box1" style="background-color:brown;">D</div>
  <div class="box1" style="background-color:lightgreen;">E</div>
</div>
<style>
#content {
  display: flex;
  width: 500px;
}

#content div {
  flex-basis: 120px;
  border: 3px solid rgba(0, 0, 0, 0.2);
}

.box {
  flex-shrink: 1;/*收缩的慢*/
}

.box1 {
  flex-shrink: 2;/*收缩的快*/
}
</style>

image-20250925012601139

水平和垂直对齐

还可以使用弹性盒子的功能让 flex 项沿主轴或交叉轴对齐。

初始情况

image-20250420173958006

然后,将下面的 CSS 添加到例子的底部:

css
div {
  display: flex;
  align-items: center;
  justify-content: space-around;
}

image-20250420174241823

带有 Smile、Laugh、Wink、Shrug & Blush 标签的五个按钮排在一个弹性容器中。通过将对齐项属性设置为中心,flex 项位于横轴的中心。通过将 justify-content 属性设置为 space-around,flex 项沿着主轴均匀间隔。

刷新一下页面,你就会看到这些按钮很好的垂直水平居中了。我们是通过下面所说的两个新的属性做到的。

align-items 控制 flex 项在交叉轴上的位置。

  • 默认的值是 stretch,其会使所有 flex 项沿着交叉轴的方向拉伸以填充父容器。如果父容器在交叉轴方向上没有固定宽度(即高度),则所有 flex 项将变得与最长的 flex 项一样长(即高度保持一致)。我们的第一个例子在默认情况下得到相等的高度的列的原因。
  • 在上面规则中我们使用的 center 值会使这些项保持其原有的高度,但是会在交叉轴居中。这就是那些按钮垂直居中的原因。
  • 你也可以设置诸如 flex-startflex-end 这样使 flex 项在交叉轴的开始或结束处对齐所有的值。查看 align-items 了解更多。

你可以用 align-self 属性覆盖 align-items 的行为。比如,你可以这样:

css
button:first-child {
  align-self: flex-end;
}

image-20250420174547360

这就是单独改变

justify-content 控制 flex 项在主轴上的位置。

  • 默认值是 flex-start,这会使所有 flex 项都位于主轴的开始处。
  • 你也可以用 flex-end 来让 flex 项到结尾处。
  • centerjustify-content 里也是可用的,可以让 flex 项在主轴居中。
  • 而我们上面用到的值 space-around 是很有用的——它会使所有 flex 项沿着主轴均匀地分布,在任意一端都会留有一点空间。
  • 还有一个值是 space-between,它和 space-around 非常相似,只是它不会在两端留下任何空间。
justify-content效果说明
flex-startimage-20250420174858541主轴开始位置摆放
flex-endimage-20250420174916039主轴结束位置摆放
centerimage-20250420174945301主轴中间拜访
space-betweenimage-20250420175003031它会使所有 flex 项沿着主轴均匀地分布,它不会在两端留下任何空间。
space-aroundimage-20250420175030863它会使所有 flex 项沿着主轴均匀地分布,在任意一端都会留有一点空间。
space-evenlyimage-20250420175053101均匀摆放,任何元素之间的间隔是相同的

flex 项排序

弹性盒子也有可以改变 flex 项的布局位置的功能,而不会影响到源顺序(即 dom 树里元素的顺序)。这也是传统布局方式很难做到的一点。

代码也很简单,将下面的 CSS 添加到示例代码下面。

css
button:first-child {
  order: 1;
}

image-20250420175405004

刷新下,然后你会看到“Smile”按钮移动到了主轴的末尾。

下面我们谈下它实现的一些细节:

  • 所有 flex 项默认的 order 值是 0。
  • order 值大的 flex 项比 order 值小的在显示顺序中更靠后。
  • 相同 order 值的 flex 项按源顺序显示。所以假如你有四个元素,其 order 值分别是 2,1,1 和 0,那么它们的显示顺序就分别是第四,第二,第三,和第一。

你也可以给 order 设置负值使它们比值为 0 的元素排得更前面。比如,你可以设置“Blush”按钮排在主轴的最前面:

css
button:last-child {
  order: -1;
}

flex 嵌套

弹性盒子也能创建一些颇为复杂的布局。设置一个元素为 flex 项,那么他同样成为一个 flex 容器,它的孩子(直接子节点)也表现为弹性盒子。

section - article
          article
          article - div - button
                    div   button
                    div   button
                          button
                          button

现在让我们看一下布局用到的代码。

image-20250420175914665

首先,我们设置<section>的子节点布局为弹性盒子。

css
section {
  display: flex;
}

下面我们给 <article> 元素设置 flex 值。特别注意这里的第二条 CSS 规则——我们设置第三个 article 元素的子节点的布局同样为 flex,但是属性值为列布局。

css
article {
  flex: 1 200px;
}

article:nth-of-type(3) {
  flex: 3 200px;
  display: flex;
  flex-flow: column;
}

接下来,我们选择了第一个<div>。首先使用 flex: 1 100px; 简单的给它一个最小的高度 100px,然后设置它的子节点(<button>元素)为 flex 项。在这里我们将它们放在一个包装行(wrap row)中,使它们居中对齐,就像我们在前面看到的单个按钮示例中做的那样。

css
article:nth-of-type(3) div:first-child {
  flex: 1 100px;
  display: flex;
  flex-flow: row wrap;
  align-items: center;
  justify-content: space-around;
}

最后,我们给按钮设置大小,有意思的是我们给它一个值为 1 的 flex 属性。如果你调整浏览器窗口宽度,你会看到这是一个非常有趣的效果。按钮将尽可能占用最多的空间,尽可能多的堆在同一条线上,但是当它们不再适合在同一条线上,他们中的一些会到下一行去。

css
button {
  flex: 1;
  margin: 5px;
  font-size: 18px;
  line-height: 1.5;
}

flex

单行

grow&shrink&basic

当你在 CSS 中写下 flex: auto 时,浏览器会将其展开为三个具体的属性值:

  1. flex-grow: 1:它允许元素占据父容器中所有剩余的可用空间
  2. flex-shrink: 1:允许元素在空间不足时缩小。
  3. flex-basis: auto:元素的初始大小取决于它的内容宽度。

刚设置flex,从左到右排列,添加再多的元素值会触发 shrink,当前是没有设置任何子元素,剩余空间不利用

image-20260206145814493

设置第三个flex-grow:1触发成长,第三个元素被强制拉伸

image-20260206150104444

收缩也是同理,但是shrink默认值就是1,所以必须设置比1大才会起作用

flex-shrink 的默认值是 1

flex-grow 的默认值是 0。默认情况下,子元素不会占用容器中的剩余空间。即使容器非常宽,子元素也只会保持它们原本的大小。

flex-grow只在拉伸的时候起作用,flex-shrink只在收缩的时候起作用

🛠️ 知识点延伸:flex 的常见缩写对照表

缩写写法展开后的完整值 (grow / shrink / basis)形象比喻
flex: initial0 1 auto默认值。不放大,可缩小,大小看内容。
flex: auto1 1 auto贪婪模式。尽可能撑满剩下的空间。
flex: none0 0 auto顽固模式。不放大也不缩小,完全固定。
flex: 11 1 0%均分模式。忽略内容宽度,大家平分空间。

justify/align

这是在有可用空间是对可用空间的利用

回到默认情况

image-20260206151454863

两者分别设置水平和垂直方向空间的利用

image-20260206151553536

可以设置某个元素的单独设置align-self

image-20260206153022520

为什么没有 justify-self

在 Flexbox 的主轴方向上,由于存在“弹性”的概念(flex-grow, flex-shrink)以及 justify-content 的多种分配方式,CSS 规范认为主轴上的单个对齐会产生逻辑冲突。

如果你想在 Flex 布局中实现类似 justify-self: right 的效果,该怎么办? 答案是使用 margin: auto

给子元素设置 margin-left: auto;,它会挤占左侧所有剩余空间,从而把自己推向最右侧。

padding 不支持 auto。只有 margin 可以通过 auto 来实现这种自动挤占空间的魔法。

在常规文档流中,margin: auto 通常只在水平方向(margin-leftmargin-right 同时设为 auto)且元素有固定宽度时,用来实现水平居中。

但在 Flexbox 布局中,margin: auto 被赋予了超能力:

  1. 吸收剩余空间:它会计算父容器在主轴或交叉轴方向上剩下的所有“多余空间”。
  2. 完全占据:如果只有一个方向设了 auto(比如 margin-left: auto),它就会把所有的剩余空间都塞到那个方向的边距里。
  3. 顶开元素:因为边距变大了,元素就会被推到对立的一侧。

在 Flex 容器里,垂直方向的 margin: auto 同样有效

margin 是元素外部的间距,它是用来控制元素与其邻居或父容器边缘之间的距离。它参与布局计算,所以可以“吸收”剩余空间。当多个 auto 竞争同一块剩余空间时,它们会等比例地瓜分这些空间。

有一点需要特别注意:margin: auto 的优先级高于 justify-content

设置margin-left: auto;真的会吧剩余空间全部利用起来

image-20260206153711627

多行

只添加换行作为初始条件

image-20260206154000002

grow&shrink

grow&shrink仍然是对空余空间的利用(行方向)

image-20260206154044573

justify/align

另一种类型对可用空间的利用

justify-content对每一行空间的分配

align-items对每一行所在列空间的分配

image-20260206154434044

可以发现目前还有一个没有控制的,那就是行间距,这个看起来是混乱的

image-20260206155028197

通过设置align-content来设置好每一行怎么排列

image-20260206155203171

Hack使用技巧

当你使用 justify-content: space-between 时,如果最后一行的元素数量不满,它们会**“一左一右”**分得很开(或者中间留出巨大的空白),看起来非常不整齐。

使用 after 伪类

css
.xxx-container {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
}
.xxx-container:after {
    content: "";
    flex: auto;
}

效果:

image

其他说明

Content (内容级:控制整行/列的间距)

这些属性作用于容器,控制内部所有元素作为一个整体如何分布。

  • justify-content: 控制主轴(默认水平)上的对齐(如 space-between, center)。
  • align-content: 仅在多行模式下有效flex-wrap: wrap)。它控制行与行之间的间距。在你的单行代码中,它是无效的。

Items (群体级:控制所有子元素的对齐)

这些属性作用于容器,为所有子元素设定一个默认的对齐方式。

  • align-items: 控制所有子元素在交叉轴(垂直)上的默认对齐方式。
  • justify-items: 在 Flexbox 中无效。它主要用于 Grid 布局。

Self (个体级:控制单个元素的对齐)

这些属性作用于子元素,允许某个元素“特立独行”。

  • align-self: 允许单个子元素覆盖容器的 align-items 设置。
  • justify-self: 在 Flexbox 中无效
属性前缀作用目标在 Flex 布局中的地位
justify-主轴 (水平)只能通过容器控制整体 (justify-content)。
align-交叉轴 (垂直)容器控整体 (items/content),子元素可控自身 (self)。
place-缩写place-itemsalign-itemsjustify-items 的合体。

“Place-” 系列

place- 系列只是缩写(Shorthand),目的是少写两行代码:

  • place-content: align-content + justify-content
  • place-items: align-items + justify-items
  • place-self: align-self + justify-self

stretch

属性类型属性名默认值行为描述
主轴分布justify-contentflex-start元素全部挤在起点(左侧)。
交叉轴对齐align-itemsstretch重点: 子元素会拉伸填满整个容器的高度(或宽度)。
多行分布align-contentstretch多行模式下,行与行之间会平分剩余空间并拉伸。
个体对齐align-selfauto默认继承父容器的 align-items(即继承 stretch)。

stretch 的核心逻辑是:“如果没有明确的大小限制,我就填满它。”

align-items: stretch (作用在容器)

这是最常见的。在你的 test.html 中:

  • 如果你删掉 .inline-blockheight: 250px,你会发现 1 号盒子会自动变得和容器一样高(或和最高的盒子一样高)。
  • 生效前提:子元素在交叉轴方向(高度)不能有固定值,或者是 height: auto

align-content: stretch (作用在容器)

flex-wrap: wrap 产生多行时,如果容器有剩余高度,stretch 会把这些剩余高度平分给每一行,让每一行看起来都“变厚”了。

align-self: stretch (作用在子元素)

即使父容器设置了 align-items: center(不拉伸),你也可以给某个特定子元素设置 align-self: stretch,让它独自一人顶天立地。

为什么有时候 stretch 不生效?

这是很多开发者踩坑的地方。stretch 有两个“天敌”:

  1. 显式尺寸:如果你设置了具体的 height(横向 flex)或 width(纵向 flex),浏览器会优先尊重你的硬性规定,stretch 就无法拉伸它。
  2. align-items 被修改:只要你把父容器的 align-items 改成了 centerflex-start,默认的拉伸效果就会消失。

我将第三个元素的高度设置为auto,接下来调整即可看到效果了:

image-20260206160222600

设置stretch

image-20260206160241552

grid布局

Flexbox 用于设计横向或纵向的布局,而 Grid 布局则被设计用于同时在两个维度上把元素按行和列排列整齐。

网格是由一系列水平及垂直的线构成的一种布局模式。根据网格,我们能够将设计元素进行排列,帮助我们设计一系列具有固定位置以及宽度的元素的页面,使我们的网站页面更加统一。

一个网格通常具有许多的列(column)*与*行(row),以及行与行、列与列之间的间隙,这个间隙一般被称为沟槽(gutter)

img

首先,我们通过把容器的 display 属性设置为 grid ,来定义一个网格。与弹性盒子一样,将父容器改为网格布局后,他的直接子项会变为网格项。把下面的 css 规则加到你的文件中。

image-20250420195041371

与弹性盒子不同的是,在定义网格后,网页并不会马上发生变化。因为 display: grid 的声明只创建了一个只有一列的网格,所以子项还是会像正常布局流那样,自上而下、一个接一个的排布。

为了让我们的容器看起来更像一个网格,我们要给刚定义的网格加一些列。那就让我们加三个宽度为200px的列。当然,这里可以用任何长度单位,包括百分比。

css
.container {
  display: grid;
  grid-template-columns: 200px 200px 200px;
}

image-20250420195153173

使用 fr 单位的灵活网格

除了长度和百分比,我们也可以用 fr 这个单位来灵活地定义网格的行与列的大小。这个单位代表网格容器中可用空间的一份,可能有点抽象,看看下面的例子吧。

使用下面的规则来修改你的网格轨道,创建 3 个宽度为 1fr 的列:

css
.container {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
}

将窗口调窄(由于示例中设定了max-width,可能需要很窄),你应该能看到每一列的宽度可以会随着可用空间变小而变小。fr 单位按比例划分了可用空间,如果没有理解,可以试着改一下数值,看看会发生什么,比如下面的代码:

css
.container {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
}

这个定义里,第一列被分配了 2fr 可用空间,余下的两列各被分配了 1fr 的可用空间,这会使得第一列的宽度更大。另外,fr可以与一般的长度单位混合使用。比如设置 grid-template-columns: 300px 2fr 1fr,那么第一列宽度是300px,剩下的两列会根据剩余的可用空间按比例分配。

备注: fr单位分配的是可用空间而非所有空间,所以如果某一格包含的内容变多了,那么整个可用空间就会减少,可用空间是不包括那些已经确定被占用的空间的。

网格间隙

使用 grid-column-gap 属性来定义列间隙;

使用 grid-row-gap 来定义行间隙;

使用 grid-gap 可以同时设定两者。

css
.container {
    display: grid;
    grid-template-columns: 2fr 1fr 1fr;
    grid-gap: 20px;
}

间隙距离可以用任何长度单位包括百分比来表示,但不能使用fr单位。

image-20250420195550358

备注: gap属性曾经有一个grid-前缀,不过后来的标准进行了修改,目的是让他们能够在不同的布局方法中都能起作用。尽管现在这个前缀不会影响语义,但为了代码的健壮性,你可以把两个属性都写上。

css
.container {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  grid-gap: 20px;
  gap: 20px;
}

重复构建Grid组

你可以使用repeat来重复构建具有某些宽度配置的某些列。举个例子,如果要创建多个等宽轨道,可以用下面的方法。

css
.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-gap: 20px;
}

和之前一样,你仍然得到了 3 个 1fr 的列。第一个传入 repeat 函数的值(3)表明了后续列宽的配置要重复多少次,而第二个值(1fr)表示需要重复的构建配置,这个配置可以具有多个长度设定。例如repeat(2, 2fr 1fr),如果你仍然不明白,可以实际测试一下效果,这相当于填入了 2fr 1fr 2fr 1fr

显式网格与隐式网格

到目前为止,我们定义过了列,但还没有管过行。但在这之前,我们要来理解一下显式网格和隐式网格。显式网格是我们用 grid-template-columnsgrid-template-rows 属性创建的。而隐式网格则是当有内容被放到网格外时才会生成的。显式网格与隐式网格的关系与弹性盒子的 main cross 轴的关系有些类似。

隐式网格中生成的行/列大小是参数默认是 auto ,大小会根据放入的内容自动调整。当然,你也可以使用grid-auto-rowsgrid-auto-columns属性手动设定隐式网格轨道的大小。下面的例子将grid-auto-rows设为了100px,然后你可以看到那些隐式网格中的行(因为这个例子里没有设定grid-template-rows,因此,所有行都位于隐式网格内)现在都是 100 像素高了。

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

image-20250420200804916

css
<style>
.container {
  display: grid;
  grid-template-columns: 200px 200px 200px;
}
.box{
  background-color: aquamarine;
  border: 3px solid aqua;
  min-height: 200px;
}
.box2{
  background-color: pink;
  border: 3px solid red;
  min-height: 400px;
}
</style>
<div class="container">
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box"></div>
    <div class="box2"></div>
    <div class="box2"></div>
    <div class="box2"></div>
    <div class="box2"></div>
    <div class="box2"></div>
  </div>

默认隐式网格大小参数auto根据内容决定:

box2拉伸box1

image-20260216132105666

minmax() 函数

100 像素高的轨道有时可能会不够用,因为时常会有比 100 像素高的内容加进去。所以,我们可以将其设定为至少 100 像素,并且能够跟随内容来自动拓展尺寸,从而保证能容纳所有内容。显而易见,你很难知道网页上某个元素的尺寸在不同情况下会变成多少,一些额外的内容或者更大的字号就会导致许多能做到像素级精准的设计出现问题。所以,我们有了minmax()函数。

minmax() 函数为一个行/列的尺寸设置了取值范围。比如设定为 minmax(100px, auto),那么尺寸就至少为 100 像素,并且如果内容尺寸大于 100 像素则会根据内容自动调整。在这里试一下把 grid-auto-rows 属性设置为minmax函数。

css
.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: minmax(100px, auto);
  grid-gap: 20px;
}

如果所有网格内的内容均小于 100 像素,看起来不会有变化。但如果在某一项中放入很长的内容或者图片,你可以看到这个格子所在的哪一行的高度变成能刚好容纳内容的高度了。注意我们修改的是 grid-auto-rows,因此只会作用于隐式网格。当然,这一项属性也可以应用于显式网格,更多内容可以参考 minmax() 页面。

动态决定列数

类似flex布局的flex-wrap:wrap

现在来试试把学到的关于网格的一切,包括 repeat 与 minmax 函数,组合起来,来实现一个非常有用的功能。某些情况下,我们需要让网格自动创建很多列来填满整个容器。通过设置grid-template-columns属性,我们可以实现这个效果,不过这一次我们会用到 repeat() 函数中的一个关键字auto-fill来替代确定的重复次数。而函数的第二个参数,我们使用minmax()函数来设定一个行/列的最小值,以及最大值 1fr

在你的文件中试试看,你也许可以用到以下的代码。

css
.container {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  grid-auto-rows: minmax(100px, auto);
  grid-gap: 20px;
}

你应该能看到形成了一个包含了许多至少 200 像素宽的列的网格,将容器填满。随着容器宽度的改变,网格会自动根据容器宽度进行调整,每一列的宽度总是大于 200 像素,并且容器总会被列填满。

image-20250420201418743

auto-fill当元素数量和较少时会留下剩余空间 image-20260216132800142auto-fit会将元素充满剩余空间 image-20260216132907663 元素较多时行为一致

基于线的元素放置

在定义完了网格之后,我们要把元素放入网格中。我们的网格有许多分隔线,第一条线的起始点与文档书写模式相关。在英文中,第一条列分隔线(即网格边缘线)在网格的最左边而第一条行分隔线在网格的最上面。而对于阿拉伯语,第一条列分隔线在网格的最右边,因为阿拉伯文是从右往左书写的。

我们根据这些分隔线来放置元素,通过以下属性来指定从那条线开始到哪条线结束。

  • grid-column-start
  • grid-column-end
  • grid-row-start
  • grid-row-end

这些属性的值均为分隔线序号,你也可以用以下缩写形式来同时指定开始与结束的线。

  • grid-column
  • grid-row

注意开始与结束的线的序号要使用/符号分开。

image-20250420201721846

文件中已经定义了一个网格以及一篇简单的文章位于网格之外。你可以看到元素已经被自动放置到了我们创建的网格中。

接下来,尝试用定义网格线的方法将所有元素放置到网格中。将以下规则加入到你的 css 的末尾:

css
header {
  grid-column: 1 / 3;
  grid-row: 1;
}

article {
  grid-column: 2;
  grid-row: 2;
}

aside {
  grid-column: 1;
  grid-row: 2;
}

footer {
  grid-column: 1 / 3;
  grid-row: 3;
}

效果:

image-20250420202012852

image-20250420204354538

备注: 你也可以用 -1 来定位到最后一条列分隔线或是行分隔线,并且可以用负数来指定倒数的某一条分隔线。但是这只能用于显式网格,对于隐式网格-1不一定能定位到最后一条分隔线。

image-20250420211616016

image-20250420211904669

单个数字就是具体的行

取值

语法类型具体写法 (示例)浏览器怎么理解 (逻辑)适用场景
自动定位auto默认值。不指定位置,让浏览器根据算法自动塞进空位。瀑布流、列表项
绝对数字3直接锁定第 3 条网格线。如果是 -1,则锁定最后一条线。侧边栏、固定布局
别名定位main-start寻找名为 main-start 的线;如果没有,就找名为 main 的区域起始线。语义化布局(如 Header)
多重同名线col 3找到第 3 条名字叫 col 的网格线。12列栅格系统、多栏重复布局
简单跨度span 2“给我两个坑”。不固定终点,只要求宽度为 2 个单元格。卡片占两格、合并单元格
语义跨度span footer“一直铺到 footer 为止”。向后寻找名为 footer 的线。动态拉伸到某个模块边缘
复杂组合span col 2“跨越 2 条名为 col 的线”。常用于跨越多个子栏目。复杂的仪表盘或报表布局

<custom-ident> 指用户自定义字符串标识符。一种CSS 数据类型;要区分大小写,值不能有任何歧义。

image-20250421001422325

<custom-ident>

在 CSS Grid 中,你不必非要数 1, 2, 3... 这些反人类的数字,你可以给特定的线起名叫 sidebar-start 或者 main-end。这个名字就是所谓的 Custom Identifier(自定义标识符)

<custom-ident> 主要有两种来源:

A. 你在 grid-template-columns / rows 中显式定义的线名

你可以用中括号 [] 给网格线命名:

css
.container {
  display: grid;
  /* 定义了三条线:[header-start]、[content]、[header-end] */
  grid-template-columns: [header-start] 1fr [content] 1fr [header-end];
}

.item {
  /* 这里的 header-start 就是 <custom-ident> */
  grid-column-start: header-start; 
}
B. 由 grid-template-areas 自动生成的线名

如果你定义了区域,浏览器会自动为你生成 -start-end 后缀的线名:

css
.container {
  grid-template-areas: "nav main";
}
.item {
  /* 即使没定义线名,也可以直接用区域名(会自动变为nav-start) */
  grid-column-start: nav; 
  /* 或者使用隐式生成的线名 */
  grid-column-start: nav-start; 
}

使用 grid-template-areas 属性放置元素

另一种往网格放元素的方式是用grid-template-areas属性,并且你要命名一些元素并在属性中使用这些名字作为一个区域。

将之前基于线的元素放置代码删除(或者重新下载一份新的文件),然后加入以下 CSS 规则:

css
.container {
  display: grid;
  grid-template-areas:
    "header header"
    "sidebar content"
    "footer footer";
  grid-template-columns: 1fr 3fr;
  gap: 20px;
}

header {
  grid-area: header;
}

article {
  grid-area: content;
}

aside {
  grid-area: sidebar;
}

footer {
  grid-area: footer;
}

刷新页面,然后你应该能看到的元素会被放到与之前相同的地方,整个过程不需要我们指定任何分隔线序号。

grid-template-areas属性的使用规则如下:

  • 你需要填满网格的每个格子
  • 对于某个横跨多个格子的元素,重复写上那个元素grid-area属性定义的区域名字
  • 所有名字只能出现在一个连续的区域,不能在不同的位置出现
  • 一个连续的区域必须是一个矩形
  • 使用.符号,让一个格子留空

你可以在文件中尽情发挥你的想象来测试各种网格排版,比如把页脚放在内容之下,或者把侧边栏一直延伸到最底。这种直观的元素放置方式很棒,你在 CSS 中看到的就是实际会出现的排版效果。

table布局

HTML 表格对于显示表格数据是很好的,但是很多年前——在浏览器中支持基本的 CSS 之前——web 开发人员过去也常常使用表格来完成整个网页布局——将它们的页眉、页脚、不同的列等等放在不同的表行和列中。这在当时是有效的,但它有很多问题——表布局是不灵活的

一个table标签之所以能够像表格那样展示,是由于 css 默认给table标签设置了一组 table 布局属性。当这些属性被应用于排列非table元素时,这种用法被称为“使用 CSS 表格”。

让我们来看一个例子。首先,创建 HTML 表单的一些简单标记。每个输入元素都有一个标签,我们还在一个段落中包含了一个标题。为了进行布局,每个标签/输入对都封装在div中。

html
<form>
  <p>First of all, tell us your name and age.</p>
  <div>
    <label for="fname">First name:</label>
    <input type="text" id="fname" />
  </div>
  <div>
    <label for="lname">Last name:</label>
    <input type="text" id="lname" />
  </div>
  <div>
    <label for="age">Age:</label>
    <input type="text" id="age" />
  </div>
</form>

现在,我们例子中的 CSS。除了使用 display 属性外,大多数 CSS 都是相当普通的。form, div, labelinput被告知要分别显示表、表行和表单元——基本上,它们会像 HTML 表格标记一样,导致标签和输入在默认情况下排列整齐。我们所要做的就是添加一些大小、边缘等等,让一切看起来都好一点,我们就完成了。

你会注意到标题段落已经给出了 display: table-caption;——这使得它看起来就像一个表格caption——同时出于设计需要,我们通过 caption-side: bottom; 告诉标题应该展示在表格的底部,即使这个p标记在源码中是在 <input> 之前。这就能让你有一点灵活的弹性。

css
html {
  font-family: sans-serif;
}

form {
  display: table;
  margin: 0 auto;
}

form div {
  display: table-row;
}

form label,
form input {
  display: table-cell;
  margin-bottom: 10px;
}

form label {
  width: 200px;
  padding-right: 5%;
  text-align: right;
}

form input {
  width: 300px;
}

form p {
  display: table-caption;
  caption-side: bottom;
  width: 300px;
  color: #999;
  font-style: italic;
}

效果:

image-20250424133034863

table.md

多列布局

多列布局是实现瀑布展示的关键!

CSS 属性 columns 用来设置元素的列宽和列数。

多列布局模组给了我们 一种把内容按列排序的方式,就像文本在报纸上排列那样。由于在 web 内容里让你的用户在一个列上通过上下滚动来阅读两篇相关的文本是一种非常低效的方式,那么把内容排列成多列可能是一种有用的技术。

要把一个块转变成多列容器 (multicol container),我们可以使用 column-count属性来告诉浏览器我们需要多少列,也可以使用column-width来告诉浏览器以至少某个宽度的尽可能多的列来填充容器。

它是一个简写属性,可在单个方便的声明中设置 column-widthcolumn-count 属性。与所有简写属性一样,任何省略的子值都将设置为其初始值

/* Column width */
columns: 18em;

/* Column count */
columns: auto;
columns: 2;

/* Both column width and count */
columns: 2 auto;
columns: auto 12em;
columns: auto auto;

取值
<'column-width'>
理想的列宽,定义为 <length> 或 auto 关键字。实际宽度可以更宽或更窄以适合可用空间。See column-width。

<'column-count'>
元素内容应分成的理想列数,定义为 <integer> 或 auto 关键字。如果此值和列的宽度都不是 auto ,则它仅指示允许的最大列数。请参阅 column-count 。

在下面这个例子中,我们从一个 class 为container<div>容器元素里边的一块 HTML 开始。

html
<div class="container">
  <h1>Multi-column layout</h1>

  <p>Paragraph 1.</p>
  <p>Paragraph 2.</p>
</div>

我们指定了该容器的column-width为 200 像素,这让浏览器创建了尽可能多的 200 像素的列来填充这一容器。接着他们共同使用剩余的空间来伸展自己的宽度。

css
.container {
  column-width: 200px;
}

注意

真正的瀑布布局,请使用:masonry-layout组件

📌 多列布局(columns)的缺陷

  1. 视觉顺序与 DOM 顺序不一致

    • 多列布局会按列逐一分配内容,导致阅读顺序变成了「从上到下,再跳到下一列」。

    • 这和我们习惯的「从左到右,从上到下」顺序不一致,不利于可访问性(Accessibility)和 SEO

    • 比如:

      .container {
        columns: 3;
      }
      .item { break-inside: avoid; }

      看上去是三列瀑布流,但实际 DOM 顺序是「第一列所有内容 → 第二列 → 第三列」。

  2. 列宽固定,无法精确控制间距和对齐

    • columns 控制的是列数或列宽,浏览器自动分配内容。
    • 无法像 Masonry 那样精确控制每个格子的定位,间隙会比较随意。
  3. 高度不均衡问题

    • 不同列的高度可能差异很大,底部「参差不齐」。
    • 真正的 Masonry 会尽量让卡片填充最短的一列,达到「平衡分布」。
  4. 交互性差

    • 如果要做 拖拽排序动态加载更多数据,多列布局会导致内容顺序混乱,难以维护。
  5. position: stickyoverflow、动画等支持较差

    • 因为多列布局内部会被浏览器「打散重排」,很多定位、动画效果不能很好应用。

📌 Masonry 布局的优势

  1. 严格按 DOM 顺序渲染(左到右、上到下),对 可访问性/SEO 友好
  2. 元素按最短列分配,保证整体视觉平衡(底部齐整感更好)。
  3. 精确控制间隙(gutter)、宽度、高度,适配性更强。
  4. 更适合动态场景(无限滚动、懒加载、拖拽排序)。
  5. 更一致的行为,不会因为浏览器对 columns 的实现差异而产生问题。

多列布局(columns) → 简单,代码少,但顺序乱、难控制、交互差,只适合纯展示型的简单图片流。

masonry-layout(组件/库/CSS Masonry) → 更贴近 Pinterest 式的「真正瀑布流」,布局可控、顺序合理、交互更强,适合生产环境

其他定位方式

浮动.md

定位.md

table.md

响应式布局.md

grid

fit/fill???

Flexbox 是一维布局(行或列),而 Grid 是二维布局(行和列同时控制)。

对于属性组:

justify-items/content/selfalign-items/content/self

最令人兴奋的是:在 Grid 中,这 6 个属性全部有效

1. 核心逻辑:Grid 单元格 (Cell)

在 Grid 中,对齐分为两个维度:

  • Inline (行) 轴:对应 justify- 系列,控制水平方向。
  • Block (块) 轴:对应 align- 系列,控制垂直方向。

2. 详细拆解

第一类:Items (容器控制所有格子内的内容)

这两个属性写在 Grid 容器上,决定了所有子元素在它们各自的“网格单元格”里怎么呆着。

  • justify-items: 控制子元素在单元格内的水平对齐。
    • stretch (默认): 填满格子宽度。
    • start / center / end: 靠左、居中、靠右。
  • align-items: 控制子元素在单元格内的垂直对齐。
    • stretch (默认): 填满格子高度。
    • start / center / end: 靠上、居中、靠下。

第二类:Self (子元素控制自己在格子里的位置)

这两个属性写在 Grid 子项上。这就是你之前在 Flex 中寻找的“失踪人口”。

  • justify-self: 允许单个子元素覆盖 justify-items。比如一排格子里的内容都靠左,你可以让其中一个单独靠右。
  • align-self: 允许单个子元素覆盖 align-items

Grid 的魅力:你可以通过 place-self: center center; 轻松让一个元素在它所属的网格格子里水平垂直绝对居中。

第三类:Content (容器控制网格整体的分布)

这两个属性写在 Grid 容器上。只有当网格的总大小小于容器大小时(比如容器 1000px,但你的网格轨道加起来才 600px),它们才起作用。

  • justify-content: 整个网格(所有的格子作为一个整体)在容器里的水平分布。
    • space-between / space-around / center
  • align-content: 整个网格在容器里的垂直分布。

3. 对比总结表

属性作用于Grid 里的表现Flex 里的表现
justify-items容器有效。控制格内水平对齐。无效
justify-self子项有效。控制单体格内水平对齐。无效
align-items容器有效。控制格内垂直对齐。有效。控制行内垂直对齐。
align-self子项有效。控制单体格内垂直对齐。有效。控制单体垂直对齐。
justify-content容器控制网格整体水平对齐。控制主轴内容对齐。
align-content容器控制网格整体垂直对齐。仅在 wrap 模式控制行间距。

margin原理

css
.container{
  background-color: pink;
  box-sizing: border-box;
  height: calc(70vh - 40px);
  width: calc(70vw - 40px);
  margin: 20px auto;
}

剩余空间(Available Space)是对齐的前提。

为什么会有“剩余空间”?

.container 是一个 block 元素:

独占一行的权利:即使你给它设置了 width: calc(70vw - 40px),在文档流中,它依然占据着整行的垂直空间(即 100% 的父容器宽度)。

剩余空间的产生:因为 70vw 小于父容器(这里是 body)的 100vw,剩下的那 30vw 左右的空间就是所谓的“剩余可用空间”。

image-20260206161639597

水平方向block 元素默认在水平方向是“贪婪”的,它拥有整行的主权,所以剩余空间是明确存在的。

垂直方向:普通文档流中,父元素的高度默认是被子元素撑开的。也就是说,在垂直方向上,如果没有显式给父元素定高,它就没有“剩余空间”的概念。

width/height

在 CSS 规范中,widthheight 的默认值都是 auto

auto 在宽度和高度上的行为逻辑完全不同,我们可以用一句话概括:宽度倾向于“占满”,高度倾向于“包裹”。

1. width: auto 的表现(以 Block 元素为例)

对于 display: block 的元素(如 div, p, h1),width: auto 的计算逻辑是**“吸收所有可用的水平空间”**。

它有一套公式: margin-left + border-left + padding-left + width + padding-right + border-right + margin-right = 父容器的宽度

  • 它的行为:如果你没有设置 margin, padding, border,那么 width: auto 就会让元素表现得和父容器一样宽。
  • 100% 的区别(重要)
    • width: 100%:强制内容区等于父容器宽度,如果你再加 paddingmargin,元素就会溢出父容器。
    • width: auto:它是“自适应”的。如果你增加了 paddingmargin,它会自动压缩内容区的宽度,确保整个盒子正好塞进父容器,不会溢出。

height: auto 的表现

与宽度截然相反,高度的 auto 遵循的是**“收缩包裹(Shrink-to-fit)”**原则。

  • 它的行为:元素的高度由其内部的子元素决定。
    • 如果里面没内容,高度就是 0
    • 如果里面有一张 100px 高的图片,高度就是 100px
  • 为什么垂直方向不能直接 margin: auto 居中? 正是因为 height: auto 不会主动去占满父容器的高度。在垂直方向上,元素默认没有“剩余空间”。除非你给父容器设了固定高度,并且给子元素也设了固定高度,手动制造出“差值”,或者使用 Flexbox 强制拉伸。

评论

评论加载中……