Flexbox构建:6种Web常用布局

155 阅读10分钟

文章介绍了 6 种 Web 常用的 Flexbox 布局:水平垂直居中,可通过设置容器属性实现单行或多行居中;等高布局,使用 Flexbox 实现更简单;均分列布局,相较传统方式,Flexbox 无需考虑诸多参数;圣杯布局,有特定的 HTML 结构和能力要求,可结合媒体查询实现响应式;12 列网格布局,比浮动构建简单;九宫格布局,给出了 HTML 和 CSS 关键代码。

水平垂直居中

Flexbox 中,可以在 Flex 容器上使用:

  • justify-contentcenter 让 Flex 项目水平居中。
  • 对于单行而言,可以使用 align-itemscenter 让 Flex 项目垂直居中;当然,使用 align-contentcenter 也可以让 Flex 项目垂直居中,但需要显式设置flex-wrap 的值为 wrapwrap-reverse
  • 对于多行而言(flex-direction 显式设置了 column),则使用 align-itemscenter 让所有 Flex 项目水平居中,再配合 justify-contentcenter 实现垂直方向居中

在 CSS 中,我们可以像下面这样编码,可以轻易实现水平垂直居中:

适合用于居中对齐。

<div class="container">
  <div class="centered">内容</div>
</div>
.container {
  display: flex;
  justify-content: center; /* 水平居中 */
  align-items: center;     /* 垂直居中 */
  height: 100vh;           /* 使容器占满整个视口高度,用于演示 */
}

.centered {
  /* 其他样式 */
}

单行或多行实现水平垂直居中,两者都可以在 Flex 容器上设置 justify-contentalign-items 属性的值为 center ,不同的是,在多行的 Flex 容器上要显式设置 flex-direction 属性的值为 column

你还可以将 justify-contentalign-self 配合起来实现水平垂直居中的效果:

  • Flex 容器上设置 justify-content 的值为 center ,让 Flex 项目实现水平居中;
  • Flex 项目上(需要实现垂直居中的 Flex 项目)上设置 align-self 的值为 center

比如:

<div class="container">
<div class="item">(^_^)</div>
<div>
.container {
  display: flex; /* 或 inline-flex */
  justify-content: center;  /* Flex 项目水平居中 */
}

.item {
  align-self: center;
}

意,从效果上可以看得出来,如果在 Flex 项目上未显式设置 align-self:center 时,Flex 项目会被拉伸,这是因为 Flex 容器的 align-items 默认值为 stretch 。如果你需要避免 Flex 项目在侧轴被拉伸,可以重置 align-items 的值为 stretch 之外的值。

这种方法也同样适用于多行或多列水平垂直居中:

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

.container > * {
  align-self: center;
}

.container--multiple {
  flex-direction: column;
}

等高布局

Web 设计师为了让页面或组件的 UI 更美观,往往会考虑像等高布局这样的效果:

image.png

如上图所示,右侧等高布局看起来总是要比左侧的不等高布局更舒服一些。虽然等高布局在 UI 上会令人感到更舒服,但在以往的布局技术中要实现等高布局还是有点麻烦的。

主要原因是我们并不知道元素的高度是多少 ,即使知道了,如果给元素上直接设置一个 height 值,很有可能就会造成内容溢出容器,甚至是打破 Web 布局。那么,使用 Flexbox (包括后面要介绍的 Grid)布局技术,实现等高布局就会轻易地多,甚至可以说是没有任何难度可言。比如,我们要实现一个等高布局的卡片组件: 果使用 Flexbox 布局技术,实现起来就很简单:

<div class="cards">
  <div class="card">
    <figure>
      <img src="thumb.png" alt="缩略图" />
    </figure>
    <h3>Card Title</h3>
    <p>Card Describe</p>
    <button>Button</button>
  </div>
  <!-- 其他 Card -->
</div>

与布局相关的 CSS 代码:

.cards {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

.card {
  display: flex;
  flex-direction: column;
  flex: 1 1 300px;
}

在这个示例中,卡片.card 和其容器 .cards 都是 Flex 容器,并且每张卡片的初始化尺寸是 300px (即 flex-basis 的值为 300px),而且容器 .cards 无法容纳所有卡片 .card 时,它会自动换行,并且最后一个卡片宽度变宽了,这是因为卡片 .card 设置了flex-grow:1 。 看下图卡片align-items: stretch即可

image.png

均分列(等分列)布局

image.png

在 Web 中均分列的布局效果很多,尤其是在移动端的开发当中,底部的菜单栏中的列大多都是均分的。

均分列又称为等分列或等列,它的最大特征就是列的宽度是相等的

以往 CSS 实现等分列都是通过百分比来计算,比如:

  • 列数(--column);
  • 列间距(--gap)。

一般情况下,列宽就是:

列宽 = (容器宽度 - (列数 - 1)× 列间距)÷ 列数

假设是一个三列,列间距为0,那么每列的宽度将会是:

.column {
  /**
  * 容器宽度 ➜ 100%
  * 列数    ➜ --columns = 3
  * 列间距  ➜  --gap = 0
  * 列宽 = ((100% - (3 - 1)  × 0) ÷ 3 = 33.3333% 
  **/
  width: 33.33333%; 
}

或者使用 calc() 函数和 CSS 的自定义属性结合:

:root {
  --container-width: 100%; /* 容器宽度 */
  --columns: 3;            /* 列数 */
  --gap: 0 ;               /* 列间距 */
}

.column {
  width: calc((var(--container-width) - (var(--columns) - 1) * var(--gap)) / var(--columns));
}

但不管是哪种方式,开发者都需要提前知道等分的列数、列间距等,对于构建一个动态的等分列,上面方案的缺陷就出来了。开发者需要不断地去做数学计算,而且是需要知道参数的情况之下才行

如果换成 Flexbox 技术来构建的话,开发者就不需要去考虑这些参数了, 如图

image.png 构建它的 HTML 结构并不复杂:

<footer>
  <div class="item">
    <Icon /> Icon name
              </div>
      <!-- 省略其他的菜单项 -->
      </footer>
        footer {
          display: flex;
        }

        .item {
          flex: 1;
          min-width: 0; /* 这行代码很重要 */
        }

        /* 菜单项图标和文字的布局 */
        .item {
          display: inline-flex;
          flex-direction: column;
          align-items: center;
          justify-content: center;
          gap: 5px;
        }

使用 flex:1 来均分列(即平均分配 Flex 容器可用空间)并不是完全可以的,它需要配合 min-width:0 一起使用。因为在 Flex 项目上显式设置 flex:1 时已重置了 flex 的初始值(flex: 0 1 auto),浏览器会把 flex:1 计算成:

flex-grow: 1;
flex-shrink: 1;
flex-basis: 0%

flex-basisauto 时, Flex 项目的宽度是 max-content (除非 Flex 容器空间完全不足)。也就是说,flex:1 时,flex-basis 值从 auto 变成了0% ,这将覆盖 Flex 项目的内在尺寸(min-content ),Flex 项目的基本尺寸现在是 0 ,但由于 flex-grow 的存在,Flex 项目会扩展以填补空的空间(Flex 容器的剩余空间)。

而实际上,在这种情况下,flex-shrink 不再做任何事情,因为所有 Flex 项目现在的宽度都是 0 ,并且正在增长以填补可用空间。只不过, Flex 容器有可能存在没有剩余空间的情况,甚至是有不足空间的情况存在。此时,flex:1 也就不能均分 Flex 容器的可用空间。

圣杯布局

圣杯布局(Holy Grail Layout)是 Web 中典型的布局模式

image.png 就上图而言,这就是一个非常普通的三列布局。对圣杯布局有一定了解的同学都应该知道,构建圣杯布局时,对 HTML 的结构是有一定的要求,即 主内容为先 。早期这样做,是让用户在 Web 页面加载缓慢时,就能先看到主内容。

<!-- HTML -->
<header>
  <!-- 页头 -->
</header>
<main>
  <!-- 页面主体,它包含两个侧边栏和一个主内容列 -->
  <article>
    <!-- 页面主内容列,需要排在 nav 和 aside 前面 -->
  </article>
  <aside>
    <!-- 侧边导航栏 -->
  </aside>
  <aside>
    <!-- 侧边内容栏,比如广告栏 -->
  </aside>
</main>
<footer>
  <!-- 页脚 -->
</footer>

对于经典的圣杯布局,它有:

  • 页头 <header>
  • 页脚 <footer>
  • 主内容 <article>
  • 左侧边栏 <aside>
  • 右侧边栏 <aside>

它应该具备的能力:

  • 在 HTML 文档的源码中,主内容 <article> 要位于两个侧边栏 <aside> 之前;
  • 页头 <header> 和页脚 <footer> 并没有固定高度,即它们的 heightauto,由其内容或相关盒模型属性值(比如 paddingmarginborder)决定大小;
  • 在垂直方向,中间三列(<main>)的高度占据 <header><footer> 之外的浏览器视窗高度,并且随着内容增多而变高;
  • 在水平方向,一般情况之下两个侧边栏也是由其内容来决定大小,但多数情况之下会给两个侧边栏设置一个固定宽度,比如左侧边栏是 220px ,右侧边栏是 320px 。中间主内容列 <article> 占据两侧边栏之外的浏览器视窗宽度,并且随着内容增加,不会出现水平滚动条。

我们来看一个真实的圣杯布局案例: 可以结合 CSS 的媒体查询,在小屏幕下调整布局,构建一个具有响应式能力的圣杯布局:


body {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-height: 100vh;
}

main {
  flex: 1 1 0%;
  min-height: 0;

  display: flex;
  gap: 1px;
}

aside {
  min-width: 220px;
  max-width: 320px;
}

aside:nth-of-type(1) {
  order: -1;
}


article {
  flex: 1 1 0%;
  min-width: 0;
}

header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
}

footer {
  display: flex;
  justify-content: center;
}



@media screen and (max-width: 800px) {
  main {
    flex-direction: column;
  }
  main aside {
    width: 100%;
    max-width: none !important;
  }

  aside:nth-of-type(1) {
    order: 1;
  }
}

页脚(Footer)的位置会随着页头(Header)和主内容(Content)高度而变化,但当页头和主内容内容较小,其高度总和小于浏览器视窗高度时,页脚要始终位于浏览器视窗底部。

对于 Sticky Footer 的布局,使用 Flexbox 再容易不过了,只需要保持主内容容器(它也是一个 Flex 项目)能随着它的父容器(Flex 容器)的剩余空间扩展。简单地说,给主内容设置一个 flex-grow 值为 1 即可。具体代码如下:

<body>
  <header>
  </header>
  <main>
  </main>
  <footer>
  </footer>
</body>
body {
min-height: 100vh;
display: flex;
flex-direction: column;
}

main {
flex-grow: 1;
}

如果你不希望浏览器视窗高度较小时,主内容的高度进行收缩,你可以将其 flex-shrink 设置为 0 ,比如:

main {
  flex: 1 0 auto;

  /* 等同于 */
  flex-grow: 1;
  flex-shrink: 0;
  flex-basis: auto;
}

同样地,为了避免页头和页脚因浏览器视窗高度较小时被挤压,建议在它们上面设置 flex-shrink 值为 0

body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

header, footer {
  flex-shrink: 0;
}

main {
  flex: 1 0 auto;
  min-height: 0;
}

12 列网格布局

12 列网格布局最早源于 960gs 网格布局系统,它和 CSS 原生的网格系统不是同一个东西。简单地说,960gs 就是将页面分成12列,有列宽和列间距,然后页面的布局划分到具体的列上面,如下图所示:

早期的 960gs 都是使用 CSS 的浮动(float)来构建的,不过现在很多 CSS 框(CSS Frameworks)中的网格系统都采用 Flexbox 来构建,比如 Bootstrap的网格系统 现在就是采用 Flexbox 布局构建的。

相对而言,使用 Flexbox 技术构建 960gs 网格系统要比浮动技术简单得多,你只需要在一个 Flex 容器放置所需要的列数,每一列对应着一个 Flex 项目。比如:

HTML

<!-- 12列:flex 容器中包含 12 个 flex 项目 -->
<flex-container>
    <flex-item> 1 of 12</flex-item>
    <!-- 中间省略 10个 flex-item -->
    <flex-item> 1 of 12</flex-item>
</flex-container>

<!-- 6列: flex 容器中包含 6个 flex 项目 -->
<flex-container>
    <flex-item> 1 of 6 </flex-item>
    <!-- 中间省略 4 个 flex-item -->
    <flex-item>1 of 6 </flex-item>
</flex-container>

<!-- 4列: flex 容器中包含 4 个 flex 项目 -->
<flex-container>
    <flex-item> 1 of 4 </flex-item>
    <!-- 中间省略 2 个 flex-item -->
    <flex-item>1 of 4 </flex-item>
</flex-container>

<!-- 3列:flex 容器中包含 3 个 flex 项目 -->
<flex-container>
    <flex-item> 1 of 3</flex-item>
    <flex-item> 1 of 3</flex-item>
    <flex-item> 1 of 3</flex-item>
</flex-container>

<!-- 2列: flex 容器中包含 2个 flex 项目 -->
<flex-container>
    <flex-item> 1 of 2</flex-item>
    <flex-item> 1 of 2</flex-item>
</flex-container>

对于 CSS 而言,主要使用 Flexbox 的 gap 属性来设置列与列之间的间距,然后在 Flex 项目上使用 flex 属性即可,比如:

flex-container {
    display: flex;
    gap: var(--gap, 1rem)
}

flex-item {
    flex: 1 1 0%;
    min-width: 0; /* 建议加上 */
}

九宫格布局

九宫格简单地说就是一个 3 × 3 的网格(三行三列),它也常用于 Web 布局中,而且你可以基于它演变出很多种不同的布局风格:

image.png

image.png 实现上图的布局效果,所需要的 HTML 结构可能会像下面这样:

<div class="card">
    <div class="card__heading">
        <h3 class="card__title">现代 Web 布局</h3>
        <p class="card__describe">使用 Flexbox 技术构建九宫格布局</p>
    </div>
    <div class="card__grid">
        <div class="card__row">
            <div class="card__column">
                <figure>
                    <img src="cat.png" alt="" />
                </figure>
            </div>
            <div class="card__column">
                <figure>
                    <img src="cat.png" alt="" />
                </figure>
                <figure>
                    <img src="cat.png" alt="" />
                </figure>
                <figure>
                    <img src="cat.png" alt="" />
                </figure>
                <figure>
                    <img src="cat.png" alt="" />
                </figure>
            </div>
        </div>
    </div>
</div>

关键的 CSS 代码:

.card {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.card__grid {
  display: flex;
  flex-direction: column;
}

.card__row {
  display: flex;
  gap: 1rem;
}

.card__column {
  flex: 1 1 calc((100% - 1rem) / 2);
  min-width: 0;

  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

.card__column:nth-child(2) figure {
  flex: 1 1 calc((100% - 1rem) / 2);
  min-width: 0;
}