CSS 零基础教程

flex-grow, flex-shrink 与 flex-basis

flex-growflex-shrinkflex-basis 这三个属性赋予了你极其精细的控制权,让你能精准决定弹性项目在容器内如何调整大小以及如何分配可用空间。

在本章节中,我们将逐一拆解这些属性,弄懂它们的组合效果,并让你具备构建动态、自适应用户界面的能力。

1. 彻底搞懂 flex-grow (放大比例)

flex-grow 属性决定了当容器内有剩余空间时,一个弹性项目相对于其他项目放大的比例。它接受一个无单位的数值,作为分配空间的比例系数。

1.1 flex-grow 基础示例

想象你的容器里有三个弹性项目:

<div class="container">
  <div class="item item-1">项目 1</div>
  <div class="item item-2">项目 2</div>
  <div class="item item-3">项目 3</div>
</div>
.container {
  display: flex;
  width: 500px; /* 固定容器宽度用于演示 */
  border: 1px solid black;
}

.item {
  background-color: lightblue;
  margin: 5px;
  text-align: center;
  padding: 10px;
}

.item-1 { flex-grow: 1; }
.item-2 { flex-grow: 1; }
.item-3 { flex-grow: 1; }

在这个场景中,三个项目都设置了 flex-grow: 1。这意味着容器内所有多余的空间将被它们平分。如果这三个项目最初的总宽度小于容器宽度,每个项目都会等比放大,去填满剩余的空间。

1.2 使用不同的 flex-grow 值

现在,我们稍微修改一下 flex-grow 的值:

.item-1 { flex-grow: 1; }
.item-2 { flex-grow: 2; }
.item-3 { flex-grow: 1; }

在这种情况下,项目 2 的放大比例是 2,而项目 1 和 3 只有 1。这意味着项目 2 放大的空间将是项目 1 和项目 3 的两倍

计算逻辑: 假设容器里有 100px 的剩余空白空间。所有的 flex-grow 值加起来是 (1 + 2 + 1 = 4)。那么,项目 2 会拿走总剩余空间的 2/4(即 50px),而项目 1 和项目 3 各自拿走 1/4(即 25px)。

1.3 flex-grow 设置为 0

如果将 flex-grow 设置为 0,那么无论有多少剩余空间,该项目都不会放大。它将严格保持其 flex-basis 指定的尺寸(如果 flex-basisauto,则保持其内容本身的尺寸)。

.item-1 { flex-grow: 0; }
.item-2 { flex-grow: 1; }
.item-3 { flex-grow: 1; }

此时,项目 1 将保持它的初始大小不发生任何放大。而项目 2 和项目 3 会平分所有剩下的空间。

重要提示:当 flex-grow 无效时

flex-grow 只有在弹性容器内确实存在剩余空间时才会生效。如果弹性项目已经填满了整个容器(甚至溢出),flex-grow 就不会让它们继续膨胀了。

2. 揭开 flex-shrink 的神秘面纱 (缩小比例)

flex-shrink 属性指定了当容器内空间不足时,一个弹性项目相对于其他项目缩小的比例。它同样接受一个无单位的数值作为缩小的比例系数。

2.1 flex-shrink 基础示例

考虑相同的 HTML 结构,但这次把容器变窄:

<div class="container">
  <div class="item item-1">项目 1</div>
  <div class="item item-2">项目 2</div>
  <div class="item item-3">项目 3</div>
</div>
.container {
  display: flex;
  width: 300px; /* 较小的容器宽度,会导致溢出 */
  border: 1px solid black;
}

.item {
  background-color: lightblue;
  margin: 5px;
  text-align: center;
  padding: 10px;
  width: 150px; /* 每个项目的初始宽度 */
}

.item-1 { flex-shrink: 1; }
.item-2 { flex-shrink: 1; }
.item-3 { flex-shrink: 1; }

每个项目初始宽度是 150px(总共 450px),但容器只有 300px 宽。如果不缩放,项目就会溢出容器。由于所有的 flex-shrink 都设置为 1(这是浏览器的默认值),所以它们会等比例地缩小,以刚好挤进 300px 的容器里。

2.2 使用不同的 flex-shrink 值

如果我们调整缩小的比例:

.item-1 { flex-shrink: 1; }
.item-2 { flex-shrink: 2; }
.item-3 { flex-shrink: 1; }

在这个场景中,项目 2 缩小的程度将是项目 1 和项目 3 的两倍。每个项目缩小的具体数值,是与其 flex-shrink 值乘以其 flex-basis(基础尺寸)成正比的。也就是说,如果一个项目的初始尺寸(flex-basis)很大,即使它的 flex-shrink 值和其他项目一样,它也会承担更多的“缩小任务”。

2.3 flex-shrink 设置为 0

如果你将 flex-shrink 设置为 0,该项目就绝对不会缩小,即使空间严重不足,它也会硬挺着保持原本的 flex-basis 尺寸,这通常会导致内容溢出容器。

.item-1 { flex-shrink: 0; }
.item-2 { flex-shrink: 1; }
.item-3 { flex-shrink: 1; }

此时,项目 1 会保持其 150px 的初始大小。项目 2 和 3 会拼命缩小以试图适配容器,但因为项目 1 拒绝缩小,最终极有可能造成溢出。

2.4 进阶:如何精确计算缩小量?

浏览器计算每个项目究竟要缩小多少像素的底层逻辑有点复杂,步骤如下:

  1. 计算总溢出量: 项目总宽度减去容器宽度。
  2. 计算每个项目的加权缩小因子: 项目的 flex-shrink 值 乘以 它的 flex-basis 值。
  3. 计算总加权缩小因子: 把所有项目的加权缩小因子加起来。
  4. 计算每个项目的缩小占比: 用该项目的加权缩小因子 除以 总加权缩小因子。
  5. 得出最终缩小量: 用该项目的缩小占比 乘以 总溢出量。
  6. 用项目的 flex-basis 减去这个最终缩小量,得出最终显示的宽度。

这套复杂的公式确保了:初始尺寸越大、flex-shrink 值越高的项目,在空间不足时,做出的“牺牲”(缩小的像素)就越多

3. 探索 flex-basis (基础尺寸)

flex-basis 属性指定了在分配多余空间(通过 flex-grow)或挤压空间(通过 flex-shrink之前,弹性项目在主轴上的初始基础尺寸

它可以是一个具体的长度值(例如 200px, 50%, 10em),也可以是关键字 content

3.1 使用长度值

flex-basis 设置为具体长度(如 200px)会强制项目在主轴上以此尺寸作为起点。随后,它才会根据 growshrink 规则进行伸缩。

.item-1 { flex-basis: 200px; }
.item-2 { flex-basis: 150px; }
.item-3 { flex-basis: 100px; }

项目 1 最初是 200px,项目 2 是 150px,项目 3 是 100px。如果有任何剩余空间,将根据它们的 flex-grow 值(如果设置了的话)再进行分配。

3.2 flex-basis: content

当设置为 content 时(这也是 flex-basis 在较新标准中的表现),项目的初始尺寸完全由其内部的内容决定。换句话说,文本有多长、图片有多宽,这个项目的初始尺寸就有多大。

.item-1 { flex-basis: content; }

此时,项目 1 的初始宽度仅仅取决于“项目 1”这几个字的宽度。内容一变,初始宽度也跟着变。

3.3 flex-basis: 0 的妙用

flex-basis 设置为 0 是一种非常常见的高级技巧。这意味着项目在主轴上的初始尺寸被归零。该项目的最终尺寸将完全、彻底地被 flex-grow 属性接管和决定。此时,项目内部文字的多少不再影响最初的空间分配。

.item-1 { flex-basis: 0; flex-grow: 1; }
.item-2 { flex-basis: 0; flex-grow: 2; }
.item-3 { flex-basis: 0; flex-grow: 1; }

因为初始宽度全是 0,所有的可用空间都变成了“剩余空间”。项目 2 分到的空间将精准地是项目 1 和 3 的两倍。即使项目里的文字长短不一,也不会干扰这种完美的 1:2:1 比例。

3.4 flex-basis vs. width / height

搞清楚 flex-basis 和普通的 width / height 的关系很重要:

  • 如果 flex-directionrow (水平排),flex-basis 就类似于 width
  • 如果 flex-directioncolumn (垂直排),flex-basis 就类似于 height

关键区别: 在弹性项目中,flex-basis 的优先级高于 widthheight。如果你同时写了 flex-basiswidth,浏览器会听 flex-basis 的,直接忽略 width

4. flex 简写属性

在实际开发中,我们极少单独写 flex-growflex-shrinkflex-basis,而是使用统一的简写属性:flex

语法格式如下:

flex: flex-grow flex-shrink flex-basis;

以下是必须掌握的几个高频简写值:

  • flex: 0 1 auto; (等同于 flex: initial): 这是浏览器的默认值。项目不会放大,但空间不足时会缩小,初始尺寸由内容决定。
  • flex: 1; (等同于 flex: 1 1 0;): 这是构建等宽列最常用的值。项目会自动放大填满空间,也会缩小,并且它的初始尺寸是 0(不受内容文字长度影响)。
  • flex: 2; (等同于 flex: 2 1 0;): 项目放大的比例是设置了 flex: 1 的项目的两倍,会缩小,初始尺寸为 0。
  • flex: 0; (等同于 flex: 0 1 0;): 项目不放大,但会缩小,初始尺寸为 0。注意:如果你希望一个元素像顽石一样绝对不缩小,你应该写成 flex: 0 0 auto;
  • flex: auto;(等同于 flex: 1 1 auto;): 项目会根据自身内容的多少自由放大和缩小。
  • flex: none; (等同于 flex: 0 0 auto;): 项目完全僵化,既不放大也不缩小,尺寸死死锁在内容大小上。

为了代码的简洁和易读,强烈建议在工作中直接使用 flex 简写属性。

5. 真实案例演示:价格表组件

让我们把今天学的知识融会贯通,在上一节导航栏的基础上,做一个响应式的产品价格表。

HTML 代码:

<div class="pricing-table">
  <div class="pricing-item">
    <h3>基础版</h3>
    <p class="price">¥99/月</p>
    <ul>
      <li>功能特性 1</li>
      <li>功能特性 2</li>
    </ul>
    <button>立即订阅</button>
  </div>
  
  <div class="pricing-item featured">
    <h3>专业版 (主推)</h3>
    <p class="price">¥199/月</p>
    <ul>
      <li>功能特性 1</li>
      <li>功能特性 2</li>
      <li>高级特性 3</li>
    </ul>
    <button>立即订阅</button>
  </div>
  
  <div class="pricing-item">
    <h3>企业版</h3>
    <p class="price">¥499/月</p>
    <ul>
      <li>功能特性 1</li>
      <li>功能特性 2</li>
      <li>高级特性 3</li>
      <li>专属客服 4</li>
    </ul>
    <button>联系销售</button>
  </div>
</div>

CSS 样式:

.pricing-table {
  display: flex;
  justify-content: space-around;
  width: 100%;
}

.pricing-item {
  border: 1px solid #ccc;
  padding: 20px;
  text-align: center;
  flex: 1; /* 神奇的 flex:1,让三个卡片平分可用空间 */
  margin: 10px;
}

.pricing-item.featured {
  border-color: gold;
  flex-grow: 1.2; /* 让这个主推版本的卡片稍微宽一点,显得更醒目 */
}

.pricing-item h3 {
  font-size: 1.5em;
  margin-bottom: 10px;
}

.pricing-item .price {
  font-size: 2em;
  font-weight: bold;
  margin-bottom: 20px;
}

.pricing-item ul {
  list-style: none;
  padding: 0;
  margin-bottom: 20px;
}

.pricing-item li {
  margin-bottom: 5px;
}

.pricing-item button {
  background-color: #4CAF50;
  color: white;
  padding: 10px 20px;
  border: none;
  cursor: pointer;
}

/* 响应式调整:针对小屏幕设备 */
@media (max-width: 768px) {
  .pricing-table {
    flex-direction: column; /* 屏幕变小时,把卡片垂直堆叠起来 */
  }
  .pricing-item {
    margin: 10px 0;
    flex: initial; /* 堆叠时重置 flex 属性,恢复自然高度 */
  }
  .pricing-item.featured {
    flex-grow: initial; /* 同时也重置特大号卡片的放大属性 */
  }
}

在这个例子中,给每个 .pricing-item 设置 flex: 1 强制它们在行内平分空间。然后,我们对主推的 .featured 卡片使用 flex-grow: 1.2 让它脱颖而出。最后,通过一段媒体查询 (media query) 实现了在手机小屏幕上的垂直堆叠降级。