浅谈 flex 和 grid 布局

最近有同事在写布局的时候,发现常用的布局用的还不熟悉,有一些属性还不了解,此外有些场景 flex 不能满足的,需要 grid 布局的,一问三不知,哈哈,这里就整理一下这两种布局常用的的相关知识吧。

Flex

flex 是弹性盒子布局,盒子内的子元素通过配置属性可以调整顺序和位置。flex 盒子默认主轴是横轴,默认主顺序时从左往右

文章中无关的修饰性 css 做了省略,下同。

设置 flex 盒子

html 复制代码
<div class="container">
  <div class="item">item: 1</div>
  <div class="item">item: 2</div>
  <div class="item">item: 3</div>
</div>
css 复制代码
.container {
  display: flex;
}

来看一下效果:

元素的初始宽(高)度

flex-basis 属性在 CSS 中用于定义 flex 容器中的子元素在分配多余空间之前的默认大小。在分配空间后,如果小于 basis 指定的大小,则以 basis 大小为准,大于的话就自动扩张。

其优先级高于子元素的 width 属性。

可选三个参数:

盒内元素填充与压缩

上面的元素并没有填充满整个 container 区域,我们来设置他们占满盒子:

css 复制代码
.item {
  flex: 1;
  border: 3px solid #663399;
}

接下来设置第一个元素占 1/2 份,其他的各占 1/4:

css 复制代码
.item:first-of-type {
  flex: 2;
}
.item {
  flex: 1;
}

flex 属性是个复合属性:

flex: flex-grow flex-shrink flex-basis

如果你只设置 flex 属性的某个值,例如 flex: 1,那么它相当于 flex: 1 1 0%。

flex-grow

表示撑开的比值,是个数字:

flex-shrink

指定 flex 元素的收缩规则,flex 元素仅在默认宽度之和大于容器的时候才会发生收缩,其收缩的大小是依据 flex-shrink 的值。比如下面的例子,页面宽度缩小后,item 3 自动被压缩了:

html 复制代码
<div class="container">
  <div class="item" style="flex: 1">item: 1</div>
  <div class="item">item: 2</div>
  <div class="item" style="width: 200px;">item: 3</div>
</div>

此时可以设置 flex-shrink: 0 表示一点都不要压缩,效果如下:

flex-shrink 不设置默认是 1,我们将第 3 个元素设置一下:flex-shrink: 0.5, 这表示要求第三个元素压缩的宽度是理应压缩的一半,我们把页面压缩到最小,宽度是 150px,前两个元素已经压无可压,宽度是 42.56px 左右,我们为了计算简单,将前两个元素设置自动扩充(不然的话浏览器会根据内部文字长度自动计算宽度,会导致误差):

我们看一下第三个元素,大概是 135.44px:

他是怎么得出来的呢?我们先假设没有设置 flex-shrink, 前两个元素是 42.56,刨去左右边框3px,是 36.56,他应该有的宽度是 150 - 36.56 - 36.56 = 76.88, 也就是说要压缩 200 - 76.88 = 123.12px, 设置了 flex-shrink: 0.5 后,需要压缩的距离缩一半,是 61.56, 也就是说现在应该的宽度是 200 - 61.56 = 138.44px,跟上边实际量出来的差了一个边框的3px。因为携带了边框,所以压缩的一半也有边框的一半:200 - 61.56 - 6/2 = 135.44

如果我们把边框除掉,值就对了:

看来 flex-shrink 是在计算好 content 值后,再计算一个边框的长度的。如果把第三个元素设置为 border-sizing, 那么就会是 132.44,此时是 150 - 42.56 - 42.56 来计算的:

gap

gap 设置子元素之间的间距,其会撑开弹性盒子的空间。废话不多说,直接看示意图:

gap 是一个复合属性:gap: column-gap row-gap

上面的第二个例子,使用 gap: 10%; 表示你希望设置的间距是父容器(主轴)尺寸的 10%,其暂时设置不了在纵轴方向的间距。

gap 属性在 flex 上还不是标准属性,卡片类的布局建议使用 grid 布局

布局方向和折行

flex-direction

可以设置弹性布局的方向,可以是行、列、倒叙等,这里不做赘述。

  • row:默认值,水平排列子元素,从左到右(在左至右的语言中)。
  • row-reverse:水平排列子元素,但方向相反,从右到左(在左至右的语言中)。
  • column:垂直排列子元素,从上到下。
  • column-reverse:垂直排列子元素,但方向相反,从下到上。
flex-wrap

表示是否超出长度换行:

flex-flow

则是两个属性的复合属性:

flex-flow: flex-direction flex-wrap

可以这么写:

css 复制代码
flex-flow: row nowrap;
order

属性则规定了弹性容器中的可伸缩项目在布局时的顺序,他可以改变原来元素在实际布局流中的顺序,数字越小的越往前。肥肠的实用:

css 复制代码
.item {
  order: 1;
}

轴线方向、正交方向布局属性

justify-content

定义浏览器如何沿着flex容器的主轴分配内容元素之间和周围的空间,直接上图说明:

需要说明的是 space-around 和 space-evenly 的区别:

  • space-around:
  1. 这个值会在每个子元素的两侧平均分配空间。
  2. 这意味着子元素两侧的空间总和是相等的。
  3. 然而,由于空间是在每个子元素的两侧分配,所以第一个和最后一个子元素与容器边缘之间的空间会少于两个子元素之间的空间。
  • space-evenly:
  1. 这个值会在子元素之间以及子元素与容器边缘之间平均分配空间。
  2. 这意味着容器的总空间被平均分成了若干部分,每两个子元素之间、第一个子元素与容器开始边缘之间、最后一个子元素与容器结束边缘之间的空间都是相等的。

如果设置为 column 布局,那么主轴就是 y 轴,justify-content 就自然是设置 y 轴轴线方向的布局。

justify-content justify-items justify-self 适用于 grid 盒子

align-items

相对于 justify-content 是在轴线方向定义布局,align-items 则定义子元素在交叉轴(正交方向)上的对齐方式,可选项如下:

  • stretch:默认值。flex 项将在交叉轴上拉伸以填充 flex 容器。如果 flex 容器在交叉轴上的大小是确定的,flex 项将被拉伸以匹配容器的大小。
  • flex-start:flex 项将在交叉轴的起点对齐。
  • flex-end:flex 项将在交叉轴的终点对齐。
  • center:flex 项将在交叉轴的中心对齐。
  • baseline:flex 项将在它们基线的起点对齐。(基线是对文本元素而言的,对于文本元素,基线是文本的底部,详情见:css flex baseline
  • start:与 flex-start 相同,是 flex-start 的别名。
  • end:与 flex-end 相同,是 flex-end 的别名。
  • self-start:flex 项将在交叉轴的自我起点对齐。
  • self-end:flex 项将在交叉轴的自我终点对齐。

下面是图示:

这里特别说明两个属性:self-start、self-end,他们用于隔离子元素之外的样式。比如这个例子:

css 复制代码
.container {
  display: flex;
  flex-direction: column;
  align-items: self-start; /* flex 项将对齐到它们自己的起点 */
}

.item {
  /* flex 项的具体样式 */
  direction: rtl; /* 容器的书写方向从右到左 */
}

flex 盒子是从左向右写的,按照 align-items: start 的规则,所有的子元素都是靠左排列的;但是如果设置了 align-items: self-start,表面上看起来没问题,但是如果其中一个子元素设置了文本方向(如上面的例子),那么这个子元素就会靠右排列了:

除了 direction,writing-modeflex-wrapunicode-bidi 等属性会影响self-*属性的效果

align-content

对于需要需要折行的 flex 布局盒子,其规定了沿着纵轴在内容项之间和周围的分配空间。

对单行弹性盒子模型无效

我们看下面的例子:

html 复制代码
<style>
#main {
    width: 70px;
    height: 300px;
    border: 1px solid #c3c3c3;
    display: flex;
    flex-wrap: wrap;
}

#main div {
    width: 70px;
    height: 70px;
}
</style>

<div id="main">
  <div style="background-color:coral;"></div>
  <div style="background-color:lightblue;"></div>
  <div style="background-color:pink;"></div>
</div>

效果:

折行之后,上下折行之间的垂直距离没有规定,所以 flex 就自动平均分配空间了。

我们设置:

css 复制代码
align-content: start;

这样所有元素就按照纵轴(主轴是行时为纵轴,反之亦然)方向,排列在起始位置:

他还有其他的可选项:center、end、space-around、space-between

align-self

align-self 属性定义flex子项单独在交叉轴(默认纵轴)方向上的对齐方式。

align-self 属性重写了容器的 align-items 属性

我们看下面的例子:

html 复制代码
<style>
#main {
    width: 220px;
    height: 300px;
    border: 1px solid black;
    display: flex;
    align-items: flex-start;
}

#main div {
    flex: 1;
}
</style>

<div id="main">
  <div style="background-color:coral;">红色</div>
  <div style="background-color:lightblue;" id="myBlueDiv">蓝色</div>  
  <div style="background-color:lightgreen;">带有更多内容的绿色 div</div>
</div>

效果:

我们设置:

css 复制代码
#myBlueDiv {
    align-self: center;
}

效果:

我们对比一下:align-items 是设置整个 flex 容器中所有子元素默认对齐方式的属性,而 align-self 是用于单个子元素来覆盖 align-items 设置的属性。通过使用 align-self,可以实现更复杂的布局,其中不同的子元素可以有不同的对齐方式。

最后贴一张网络上的对比图:

Grid

Grid 布局是一种二维布局系统,用于在两个维度(行和列)上布置元素。通过给盒子元素设置 display: grid; 来声明一个网格盒子。

设置网格盒子

我们看一个基础的例子:

grid 属性是一个复合属性,它是一个简写形式,用于设置多个 grid-* 属性。使用 grid 属性可以更简洁地定义网格布局,而不需要分别设置每个相关的子属性。

其格式定义如下:

css 复制代码
grid: grid-template | grid-template-rows / grid-template-columns;
grid: grid-template-rows / grid-template-columns;
grid: grid-template / grid-template-rows grid-template-columns;
grid: grid-template-rows / grid-template-columns grid-template-areas;
grid: grid-auto-flow / grid-auto-columns / grid-auto-rows;
grid: grid-auto-flow / grid-auto-columns;
grid: grid-auto-flow;

其中,各个部分的含义如下:

  • grid-template:这是 grid-template-rows、grid-template-columns 和 grid-template-areas 的简写形式,用于定义网格容器的行和列的大小以及网格区域的名称。
  • grid-template-rows:用于定义网格容器的行大小。
  • grid-template-columns:用于定义网格容器的列大小。
  • grid-template-areas:用于定义网格区域的名称。
  • grid-auto-flow:用于定义网格项的自动放置行为(如 row、column、dense)。
  • grid-auto-columns:用于定义网格容器中自动创建的列的大小。
  • grid-auto-rows:用于定义网格容器中自动创建的行的大小。

你可以使用斜线(/)来分隔行和列的设置。如果你只设置行或列,那么未设置的部分将使用默认值。如果你只设置行或列,那么未设置的部分将使用默认值。

我们逐条来距离里讲解各个属性。

grid-template-rows / grid-template-columns / grid-template-areas

看代码:

html 复制代码
<style>
.grid-container {
  display: grid;
  grid-template-columns: auto auto auto auto;
  /* 使用grid-template-rows 属性定义第一行高度为 100px,第二行高度为 300px。 */
  grid-template-rows: 100px 300px;
  grid-gap: 10px;
  background-color: #2196F3;
  padding: 10px;
}

.grid-container > div {
  background-color: rgba(255, 255, 255, 0.8);
}
</style>

<div class="grid-container">
  <div class="item1">1</div>
  <div class="item2">2</div>
  <div class="item3">3</div>  
  <div class="item4">4</div>
  <div class="item5">5</div>
  <div class="item6">6</div>
  <div class="item7">7</div>
  <div class="item8">8</div>
</div>

grid-template-rows 定义各个行的高度,依次往后追加即可:

grid-template-columns 定义各个列的宽度,使用方式同上。本文的例子给了 auto,表示自动平分宽度,你也可以这么设置:

css 复制代码
  grid-template-columns: 100px 200px auto auto; /* 创建四列,宽度分别为100px、200px和自动 */

注意:grid 布局会以 column 为准,column定义了几列,就会渲染几列,剩余的就新增行来展示,如果没有定义多余的行,那么行高自动为 auto。

此外,上面设置了 grid-gap 后,子元素的宽度设置为 auto 的会被自动压缩。

grid-template-areas 的功能:

  • 定义区域名称:你可以为网格中的每个区域指定一个名称。
  • 多行和多列:你可以定义跨越多行和多列的区域。
  • 空区域:你可以使用 . 来表示一个空的单元格。
  • 重复行:你可以省略某些行,浏览器会自动重复上一行的定义。
  • 项目放置:你可以使用 grid-area 属性来引用这些定义的区域名称,从而放置项目。

还是上面的例子,假设我们给第一个子元素定义具名:

css 复制代码
.item1 {
  grid-area: myArea;
}

.grid-container {
  display: grid;
  grid-template-areas: 'myArea myArea myArea myArea myArea';
  grid-gap: 10px;
  background-color: #2196F3;
  padding: 10px;
}

那么,container 就会实现将 item1 元素横跨 5 列的操作,从而实现 colspan 的功能:

如果少写几列,其余的元素则会依次往后排:

grid-template-areas: 'myArea myArea myArea myArea'

还可以用来布局:

css 复制代码
.item1 { grid-area: header; }
.item2 { grid-area: menu; }
.item3 { grid-area: main; }
.item4 { grid-area: right; }
.item5 { grid-area: footer; }

.grid-container {
  display: grid;
  grid-template-areas:
    'header header header header header header'
    'menu main main main right right'
    'menu footer footer footer footer footer';
  grid-gap: 10px;
  background-color: #2196F3;
  padding: 10px;
}

在具名布局中,还可以使用 . 号表示没有名称的网格项:

css 复制代码
grid-template-areas: 'myArea myArea . . .';
css 复制代码
 grid-template-areas: 'myArea myArea . . .' 'myArea myArea . . .';

grid-template-areas 的项数表示有多少列,如果为多维,则 myArea 位置一定得能拼凑为一个矩形,否则配置失效

最后看复合属性:

css 复制代码
grid-template: 1fr 1fr / 1fr 1fr;

上面的代码,规定了行、列的比例都是1:1,相当于2行2列的方格子。

另外说一下,grid 布局是按照"报纸阅读顺序"来排列数据的,比如:grid-template: auto / 1fr 1fr:

css 复制代码
grid: 
    'header header header' 50px
    'main sidebar footer' 1fr
    'main sidebar footer' 1fr
    'footer footer footer' 50px
    / 1fr 2fr 1fr;

其定义了 四行,每一行有3列,并设置了每一行的高度,1fr 2fr 1fr 则定义了 3 列的宽度。其等价于:

css 复制代码
  grid-template-areas:
    'header header header'
    'main sidebar footer'
    'main sidebar footer'
    'footer footer footer';
  grid-template-rows: 50px 1fr 1fr 50px;
  grid-template-columns: 1fr 2fr 1fr;

fr 类似于 flex 的数字,表示单元格占比的分数。1fr 2fr 1fr 表示 1:2:1 地平分盒子宽度。

grid-row-gap / grid-column-gap

他们分别定义行、列方向上的间距,复合属性是 grid-gap。这里不做赘述。

可简写为 row-gap / column-gap

grid-[column | row]-[start | end]

表示行或列从第几个单元格开始布局,举个例子:

css 复制代码
grid-template-columns: auto auto auto auto;

.item1 {
  // 第一个元素
  grid-column-start: 2;
}

如果设置超过规定的最大列数,就会覆盖最大列数。比如 grid-column-start: 9, 就会撑开一个 9 列的盒子。

grid-auto-flow

grid-auto-flow 属性指定自动布局算法怎样运作,精确指定在网格中被自动布局的元素怎样排列。

语法格式:

css 复制代码
grid-auto-flow: [ row | column ] || dense

其中:

  • row: 多的格子一行一行陈列。默认值。
  • column: 多的格子一列一列排列。
  • dense: 多的格子空白填充。

默认设置一个 grid:

css 复制代码
grid-template: auto / 1fr 1fr;

其默认就有 grid-auto-flow 属性,是 row,就是按照上面说的"报纸阅读顺序"排列的。

如果你这么配置:

css 复制代码
grid-auto-flow: column;

那么"自动流动"状态的子元素全部都一列一列显示:

上面是行为auto的情况,如果设置了两行:

css 复制代码
grid-template-rows: 1fr 1fr;
grid-auto-flow: column;

那么,效果图如下:

此时设置列是无效的,如果有多余的元素,会依次往后追加列

还有一个 dense 值。他的意思是在稀疏布局时,优先考虑稀疏的空缺部分。

我们上面知道,grid-column-start: 2 设置后,第一个单元格会空缺,我们认为此时就是稀疏布局的,可以这样设置:

css 复制代码
grid-auto-flow: dense;

此时再看看效果:

grid-auto-rows / grid-auto-columns

grid-auto-rows 是在网格元素没有使用 grid-template-rows 来指定大小时,采用隐式创建 grid 流来布局。不要与 grid-template-rows 混合使用。

其可选项和效果图一看就明白:

他的作用就是设置一个保底的行的高度。

minmax(30px, auto) 表示每一行的高度将有一个最小值和一个最大值:

  • 30px 是行的最小高度,意味着行的高度至少为 30 像素。
  • auto 是行的最大高度,意味着行的高度可以根据其内容的大小自动调整。

因此,这个设置允许行的高度在 30 像素和内容的实际高度之间变化,确保行至少有 30 像素的高度,同时在内容较多时可以扩展到更大的高度。

grid-auto-columns 同理,这里不再赘述。

例子解释

现在解释一下文章一开头那三个基础的例子。

  1. grid: auto-flow / 1fr 1fr 1fr

/ 分割行和列的配置,表示行使用 auto-flow 项目会按默认顺序自动流动到下一行;1fr 1fr 1fr 表示创建三个列。(第二个元素另外设置了宽度)

  1. grid: auto-flow dense / 40px 40px 1fr

第二个例子多了个 dense,导致第三个元素填充到了空白区域:

  1. grid: repeat(3, 80px) / auto-flow

第三个例子,repeat(3, 80px) 表示创建三行,每行的高度为 80 像素。列使用 auto-flow 表示会依次往后追加元素,列不够就添加列。

这里需要区分 auto-flow 值和 grid-auto-flow 属性,他们不是一回事。auto-flow 是 grid 属性的简写中使用的一个关键词,表示默认的流动方式,通常与 grid-auto-flow 属性的默认值一致。

grid-row / grid-column

grid-row(column) 属性定义了网格元素行(列)的开始和结束位置。

其语法格式:

grid-row: grid-row-start / grid-row-end

我们直接一个例子的上:

css 复制代码
// 设置 6列
.container {
  grid-template-columns: auto auto auto auto auto auto;
}

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

这表示从第1行开始,span 2 表示该项目将跨越两行:

思考一下,这是不是跟 grid-area 有些像啊,都是类似表格的 span 操作

那如果我们设置列呢:

css 复制代码
.item1 {
  grid-column: 1 / span 5;
}

他应该怎么布局?从第1列开始,横跨5列:

轴线方向、正交方向布局属性

justify-content

grid-auto-flow: row 的情况:

grid-auto-flow: column 的情况:

justify-items

为所有盒中的项目定义了默认的 justify-self ,可以使这些项目以默认方式沿适当轴线对齐到每个盒子。

grid-auto-flow: row 的情况:

justify-content 的交叉轴的宽度/高度会自动适应最长的那个,justify-item 会自动拉伸或最小适配,其针对的是容器内部的内容,而不是容器本身。

align-items

本节例子统一加样式:grid-auto-rows: 80px

grid-auto-flow: row 的情况:

grid-auto-flow: column 的情况:

align-content

我们先设置行高:

grid-auto-rows: 40px

这样,align-items 设置后效果:

其注重各个子项在各自的单元格里的对齐方式。会导致 grid-auto-rows 失效。

如果换成 align-content:

同 flex 一样,align-content 用于设置空白(多余)部分如何布局,水平方向的多余空间如何分配,用justify-content来设置,设置值为left,表示所有子元素靠左,空白区域留在右侧,类似的,align-content用来设置垂直方向的多余空间分配。

Grid 在线小游戏

Grid garden

Grid 练习

练习1

用 grid 布局实现一个微软的 logo !

参考答案:伪元素模拟实现微软logo阴影

练习2

借助 grid 布局,使用两种方式实现一下这样的图片布局:

相关推荐
修炼室28 分钟前
从拥堵到畅通:HTTP/2 如何解决 Web 性能瓶颈?
前端·网络协议·http
让开,我要吃人了1 小时前
HarmonyOS鸿蒙开发实战( Beta5.0)页面加载效果实现详解实践案例
开发语言·前端·华为·移动开发·harmonyos·鸿蒙·鸿蒙系统
洞窝技术2 小时前
重塑前端开发:如何利用 micro-app 实现高效微前端架构
前端·javascript
吕彬-前端2 小时前
使用vite+react+ts+Ant Design开发后台管理项目(三)
前端·javascript·react.js
想做一只快乐的修狗2 小时前
【react案例】实现评论列表
前端·react.js·前端框架
m0_719414562 小时前
【Vue.js基础】
前端·vue.js·flutter
fxshy2 小时前
01-Cesium添加泛光线
开发语言·前端·javascript
Hanking652032 小时前
Android程序员怎么从零到一开发一个自己的AI小程序并上线
前端·微信小程序·小程序·云开发
聊天宝快捷回复3 小时前
必收藏,售后客服日常回复必备的话术 (精华版)
java·前端·数据库·经验分享·微信·职场发展·快捷回复
v(z_xiansheng88)3 小时前
markdown
前端·macos