深入理解 Flex 布局

717 阅读13分钟

导读

在现代网页设计中,布局是至关重要的一部分。传统的布局方式如 floatinline-blocktable 已经满足不了当前复杂网页设计的需求。Flexbox(弹性盒子布局)作为一种强大而灵活的布局模式,可以帮助我们轻松地创建复杂的页面布局。

什么是 Flex 布局?

Flex 布局(Flexbox),或称为弹性盒子布局,是 CSS3 中的一种布局模式。它旨在提高盒模型中的子元素的对齐、方向和顺序,使其更容易在容器内排列。Flexbox 是为了解决传统布局方法的不足而设计的,尤其适合于复杂的页面组件布局,例如导航栏、卡片布局、响应式设计等。

Flex 布局的基本概念

要使用 Flex 布局,我们首先需要将一个元素的 display 属性设置为 flexinline-flex。这个元素即为容器(container),而容器内部的直接子元素为项(items)或者弹性子元素(flex item)。一旦设置了 display: flex,容器的子元素便可以享受 Flex 布局的各种好处。

1.drawio.png

子元素按照主轴线排列,主轴的方向为主起点(左)到主终点(右)。垂直与主轴的是副轴。方向从副起点(上)到副终点(下)。

容器属性

容器属性是应用在 Flex 容器上的属性,用于控制容器内部项的布局。

flex-direction 属性

flex-direction 属性定义了主轴的方向,即项目在容器内的排列方向。可以取值为:

flex-direction.png

  • row(默认值):项目按行排列,从左到右。
  • row-reverse:项目按行反向排列,从右到左。
  • column:项目按列排列,从上到下。
  • column-reverse:项目按列反向排列,从下到上。

flex-wrap 属性

flex-wrap 属性定义如果一条轴线排不下,是否换行。可以取值为:

flex-wrap.png

  • nowrap(默认值):不换行,所有项目排在一行。
  • wrap:换行,项目会在多行排列。
  • wrap-reverse:换行,并反向排列。

注意:启用(设置了 wrap 或者 wrap-reverse)换行后,子元素不再根据 flex-shrink 值收缩,任何超过弹性容器的子元素都会换行显示。

当设置 column 或者 column-reverse,flex-wrap 会允许弹性子元素切换到新的一列,不过仅在限制了容器高度的情况下才会发生,否则容器会扩展高度以包含所有子元素。

justify-content 属性

justify-content 属性定义了项目在主轴(横轴)上的对齐方式。可以取值为:

justify-content.png

  • flex-start(默认值):项目在主轴的起点对齐。
  • flex-end:项目在主轴的终点对齐。
  • center:项目在主轴居中对齐。
  • space-between:项目在主轴上均匀分布,首尾两端对齐。
  • space-around:项目在主轴上均匀分布,每个项目前后间隔相等。

注意:justify-content 在子元素未填满容器时,才可以控制子元素在主轴方向上的间距。

间距是在元素的外边距之后进行计算的,而且 flex-grow 的也要考虑进来。简单点说,如果任意子元素的 flex-grow 的值不为 0,或者任意子元素在主轴方向上的外边距是 auto,justify-content 就失效了。

align-items 属性

align-items 属性:定义了项目在交叉轴(副轴)上的对齐方式。可以取值为:

align-items.png

  • flex-start:项目在交叉轴的起点对齐。
  • flex-end:项目在交叉轴的终点对齐。
  • center:项目在交叉轴居中对齐。
  • baseline:项目的第一行文字的基线对齐。
  • stretch(默认值):如果项目未设置高度或设为 auto,则将项目拉伸到填满容器。

注意:stretch 作为默认值,在水平排列的时候,它让所有子元素填充容器的高度。在垂直排列的时候,则是让所有子元素填充容器的宽度,因此它可以实现登高列。

align-content 属性

align-content 属性定义了多根轴线的对齐方式。它在有多根轴线的情况下才起作用。可以取值为:

align-content.png

  • flex-start:轴线在交叉轴的起点对齐。
  • flex-end:轴线在交叉轴的终点对齐。
  • center:轴线在交叉轴居中对齐。
  • space-between:轴线在交叉轴上均匀分布,首尾两端对齐。
  • space-around:轴线在交叉轴上均匀分布,每根轴线前后间隔相等。
  • stretch(默认值):轴线将占满整个交叉轴。

注意:如果开启了换行(flex-warp:wrap 或 wrap-reverse),align-content 属性就可以控制容器内在副轴方向上每行之间的间距。

项目属性

这些属性应用于 Flex 项目,用于控制项目自身的排列方式。

  • order:定义项目的排列顺序。数值越小,排列越靠前,默认为 0
  • flex-grow:定义项目的放大比例。默认为 0,即如果有剩余空间,也不放大。
  • flex-shrink:定义项目的缩小比例。默认为 1,即如果空间不足,该项目将缩小。
  • flex-basis:定义了在分配多余空间之前,项目占据的主轴空间。默认为 auto,即项目的本来大小。
  • align-self:允许单个项目有与其他项目不同的对齐方式,可覆盖 align-items 属性。可以取值为 autoflex-startflex-endcenterbaselinestretch

flex-basis 属性

flex-basis 属性定义元素大小的基准值,及初始的“主尺寸”。当 flex-basis 属性值为 auto 时,如果设置了 width 值,元素的宽度等于 width 值,如果没有则会根据内容宽度变化。如果 flex-basis 属性值不为 auto,则设置的 width 属性会被忽略。

flex.drawio.png

每个弹性子元素的初始主尺寸确定后,它们可能需要在主轴方向扩大或者收缩来适应容器的大小。这时候就需要设置 flex-grow 或者 flex-shrink 来决定缩放的规则。

flex-grow 属性

子元素在计算 flex-basis 属性设置的初始值的宽度后,主轴上如果还有留白(剩余的宽度或高度),则这个留白的值会按照 flex-grow 属性的(非负数)值分配给每个子元素。

flex.drawio.png

(拥有相同 flex-grow 值的子元素,会分配相同比例的剩余空间)

如果值为 0,则子元素的宽度不会变化,不会超过 flex-basis 属性设置的初始值。如果值大于 0,值越大,分配的留白的比例越大。例如 flex-grow: 2 分配的留白就是 flex-grow: 1 的子元素的 2 倍。

flex-shrink 属性

flex-shrink 属性遵循和 flex-grow 属性相似的原则。flex-grow 属性分配的主轴上的留白空间。而 flex-shrink 属性则分配的是所有子元素超过主轴空间的超出部分的空间。每个子元素会按 flex-shrink 属性收缩(宽度或高度),直到适应容器的大小。

flex.drawio.png

flex-shrink 属性为 0,则表示如果所有子元素的内容超出了容器主轴方向的尺寸,此子元素不会收缩尺寸。

align-self 属性

align-self 属性控制弹性子元素沿着容器副轴方向上的对齐方式。

flex.drawio.png

与容器的 align-items 属性效果相同,但它能单独给弹性子容器设定不同的对齐方式。

order 属性

通常,弹性子元素会依照在 HTML 代码中的顺序排序。沿着主轴方向,从主轴的起点开始排序。使用 order 属性后,能改变子元素的排列顺序。可以将其指定为任意正负值,值相同的子元素会按 HTML 代码中的顺序排序。

注意:谨慎使用 order 属性,因为它会影响网站的可访问性。例如:通常使用 tab 键浏览元素时,会按 HTML 代码中的顺序切换,但使用 order 属性会改变顺序,可能会对用户使用产生困惑。

Flex 布局的应用场景

Flex 布局它提供了一种高效的方式来分配容器中元素的空间,并对其对齐和排序进行控制。Flex 布局能自动适应不同的屏幕大小和方向变化,使其在构建响应式设计时尤为有用。下面是 Flex 布局常见的应用场景和使用实例。

水平和垂直居中对齐

Flex 布局可以轻松实现容器内的元素水平和垂直居中对齐,这是传统 CSS 布局技术难以做到的。

应用场景

  • 在一个容器中居中显示文本或图标;
  • 制作弹窗、模态框等 UI 元素,确保它们在页面中央居中显示;

代码示例:

<div class="container">
    <div class="item">Centered Item</div>
</div>
.container {
    display: flex;
    justify-content: center; /* 水平居中 */
    align-items: center;    /* 垂直居中 */
    height: 300px;
    border: 1px solid #ddd;
}

.item {
    padding: 10px;
    background-color: #f0f0f0;
}

实现等高列布局

传统的浮动布局(float)在实现等高列时比较困难,而 Flex 布局能轻松实现容器中所有子元素的等高。

应用场景

  • 创建卡片布局、产品列表或博客文章列表时,保持所有项的高度一致;
  • 构建侧边栏布局,确保主内容和侧边栏的高度相同;

代码示例:

<div class="container">
    <div class="column">Column 1</div>
    <div class="column">Column 2 with more content</div>
    <div class="column">Column 3</div>
</div>
.container {
    display: flex;
}

.column {
    flex: 1; /* 各列等宽 */
    padding: 20px;
    background-color: #f4f4f4;
    border: 1px solid #ddd;
    margin: 5px;
}

创建响应式导航栏

Flex 布局非常适合用于创建响应式的导航栏,可以根据容器宽度自动调整导航项的排列方式和间距。

应用场景

  • 构建水平或垂直的导航菜单;
  • 创建在小屏幕上可以折叠的导航栏;

代码示例:

<nav class="navbar">
    <a href="#">Home</a>
    <a href="#">About</a>
    <a href="#">Services</a>
    <a href="#">Contact</a>
</nav>
.navbar {
    display: flex;
    justify-content: space-around; /* 等间距排列 */
    background-color: #333;
    padding: 10px;
}

.navbar a {
    color: white;
    text-decoration: none;
    padding: 8px 16px;
}

.navbar a:hover {
    background-color: #575757;
}

制作响应式(3栏)布局

Flex 布局被最广泛使用的一个用途就是制作相应式的布局。

应用场景:

  • 创建横向弹性布局;
  • 创建纵向弹性布局;

代码示例:横向弹性布局

<section class="container">
    <aside class="aside"></aside>
    <main class="main"></main>
</section>
.container{
  display: flex;
  flex-direction: row;
  flex-warp: nowrap;
  width: 100%;
  overflow: hidden;
}

.aside {
  /* 不缩放,固定宽度 */
  flex-shrink: 0;
  width: 200px;
  overflow: hidden;
}

.main { 
  flex: 1;
  height: 100%;
  overflow: auto;
}

代码示例:纵向弹性布局

<section class="container">
    <header class="header"></header>
    <main class="main"></main>
    <footer class="footer"></footer>
</section>
.container{
  display: flex;
  flex-direction: column;
  flex-warp: nowrap;
  width: 100%;
  overflow: hidden;
}

.header,
.footer {
  /* 不缩放,固定宽度 */
  flex-shrink: 0;
  margin: 0;
  width: 100%;
  height: 50px;
  overflow: hidden;
}

.main { 
  flex: 1;
  margin: 0;
  width: 100%;
  overflow: auto;
}

制作响应式网格布局

虽然 Flex 布局主要用于一维布局,但也可以用来创建简单的响应式网格布局,尤其是在需要灵活调整列数的场景下。

应用场景

  • 创建响应式图片画廊;
  • 构建自适应卡片网格或商品展示页面;

代码示例:

<div class="gallery">
    <div class="item">Item 1</div>
    <div class="item">Item 2</div>
    <div class="item">Item 3</div>
    <div class="item">Item 4</div>
</div>
.gallery {
    display: flex;
    flex-wrap: wrap; /* 允许换行 */
}

.item {
    flex: 1 1 calc(33.333% - 20px); /* 每行三列,保留间隙 */
    margin: 10px;
    background-color: #ddd;
    padding: 20px;
    box-sizing: border-box; /* 包括填充在内计算宽度 */
}

排列不规则的内容

Flex 布局允许灵活地排列和对齐不规则尺寸的内容。它可以通过调整项目的增长和收缩来动态填充空白空间,或调整项目的顺序。

应用场景

  • 在特定的布局中打破传统的线性顺序;
  • 根据需求灵活调整内容的顺序和大小,创建自定义的内容展示方式;

代码示例:

<div class="container">
    <div class="item item1">Item 1</div>
    <div class="item item2">Item 2</div>
    <div class="item item3">Item 3</div>
</div>
.container {
    display: flex;
}

.item1 {
    flex-grow: 2; /* 放大占用更多空间 */
}

.item2 {
    flex-grow: 1;
}

.item3 {
    flex-grow: 1;
    order: -1; /* 调整顺序 */
}

构建固定底部导航栏

在移动端应用中,固定的底部导航栏是常见的设计。Flex 布局可以方便地创建固定在页面底部的导航栏,并确保其内容根据容器宽度自动调整。

应用场景:

  • 创建适用于移动设备的固定底部导航栏;
  • 构建响应式的底部操作栏,保证内容自适应不同设备;

代码示例:

<div class="footer-nav">
    <a href="#">Home</a>
    <a href="#">Profile</a>
    <a href="#">Settings</a>
</div>
.footer-nav {
    display: flex;
    justify-content: space-around; /* 等间距排列 */
    position: fixed;
    bottom: 0;
    width: 100%;
    background-color: #333;
    padding: 10px 0;
}

.footer-nav a {
    color: white;
    text-decoration: none;
    text-align: center;
    flex: 1;
}

Flex 布局的优缺点

Flex 布局提供了比传统布局模型(如 floatposition)更高的灵活性和更简便的对齐、排序能力。然而,和其他技术一样,Flex 布局也有其优缺点。下面我们来详细讨论 Flex 布局的优缺点。

优点

  • 简化布局:不需要使用浮动、清除浮动等繁琐的技巧,简单的属性设置即可实现复杂的布局。
  • 响应式设计:Flex 布局非常适合响应式设计,可以根据容器大小调整布局。
  • 兼容性较好:主流浏览器都已支持 Flex 布局。

缺点

  • 学习曲线:Flex 布局的概念和属性比较多,初学者可能需要一些时间来掌握。
  • 性能问题:在处理大量复杂布局时,可能会影响性能。

Flex 布局的缺陷(Bugs)

Flex 布局虽然是一个强大而灵活的布局系统,但它目前也存在着一些缺陷,让我们一起了解一下吧。

Flexbugs 是 GitHub 上一个记录已知 Flexbox 的浏览器 bug 的项目,项目中介绍了在哪些环境下会导致 bug,并给出了大部分 bug 的解决方案。如果你遇到了 Flex 布局导致的问题,不妨到 Flexbugs 看看,是否有记录相应的问题和解决方案。

另外,Flex 布局在(屏幕很宽),页面很宽的时候,有一个奇怪的行为。浏览器在加载内容时,它会先渐进式的先显示一部分内容到屏幕,即使此时的剩余部分内容还在加载中。

具体说就是使用 flex-direction: row 实现页面 1 行 3 列布局,其中两列内容加载完了,第三列内容还在加载中,浏览器可能会先显示前两列的内容,然后等剩余的内容加载完,浏览器又会根据3列加载完的内容宽度重新计算弹性布局的每列的宽度,重新渲染页面。

这个问题在使用 Flex 布局的时候需要特别注意,相关问题的具体介绍,请阅读《Don't use flexbox for overall page layout》。

总结

Flex 布局使得复杂布局的实现变得更加容易和直观。无论是水平居中、垂直居中,还是创建响应式布局,Flex 布局都能提供简单而高效的解决方案。掌握 Flex 布局的使用,不仅可以简化代码,也可以提升网页设计的整体体验。

通过理解和实践 Flex 布局,你将能够更好地应对现代网页设计的挑战,为用户提供优雅、流畅的体验。

最后要提醒一下大家,一旦你熟悉了它,你可能会想要在页面的每一个地方都使用它,不过你还是应该依靠正常的文档流,只在必要的时候才使用 Flex 布局。使用的时候也要注意 Flex 布局的缺陷问题。