这段声明定义了两个显式列轨道:

.row {
  display: grid;
  grid-template-columns:
    minmax(8rem, 1fr)
    minmax(0, 3fr);
}

两列并非固定占据 25%75%,而是各自遵循一组宽度规则:

  • 左列最窄为 8rem,按 1 份分配余下空间。
  • 右列最窄为 0,按 3 份分配余下空间。

这种写法适合“标签 + 主内容”布局:左列至少为 8rem,右列取得其余宽度;容器变窄时,右列可以缩窄。

grid-template-columns 描述的是列轨道

grid-template-columns 后面的每个轨道尺寸对应一列:

grid-template-columns: 12rem 1fr;

这段代码定义两列。第一列宽 12rem,第二列用 1fr 占据余下空间:

┌──────────────┬──────────────────────────────┐
│    12rem     │             1fr              │
└──────────────┴──────────────────────────────┘

如果设置了 column-gapgap,列间距也会占用容器宽度,但不属于任何一列。

fr 按权重分配余下宽度

Grid 扣除固定宽度、最小宽度和列间距后,把余下宽度分给使用 fr 的列。fr 前面的数值表示各列的分配权重,CSS Grid 规范称为 flex factor。CSS Grid Layout Module Level 2:Flexible Lengths

例如:

grid-template-columns: 1fr 2fr;

如果没有其他宽度约束,两列按 1 : 2 分配可用宽度:第一列占三分之一,第二列占三分之二。

同理,1fr 3fr 的分配比例是:

1 : 3

在没有 gap、两列都未触及下限时,列宽比例为:

左列 = 1 / (1 + 3) = 25%
右列 = 3 / (1 + 3) = 75%

fr 不是百分比。这里的 1fr3fr 合计为 4fr,所以分别得到 25%75%;某一列触及下限后,比例随之改变。

minmax() 同时声明下限和上限

minmax(min, max) 为 Grid 轨道设置尺寸范围:第一个参数是下限,第二个参数是上限所用的尺寸函数。第二个参数可以使用 fr,表示该轨道分配余下空间时的权重。CSS Grid Layout Module Level 2:Track Sizing Functions

minmax(8rem, 1fr)

这段代码可以读作:

最小轨道尺寸:8rem
弹性系数:    1

这里不能按字面比较 818rem 是长度,1fr 是分配权重;后者需要结合容器宽度、其他轨道和间距才能换算成长度。minmax() 比较的是两种规则算出的轨道宽度,而不是单位前面的数字。

rem 相对于根元素的字体大小。只有在根元素计算后的 font-size16px 时,8rem 才等于 128px;它不是固定的像素换算。CSS Values and Units Module Level 4:Font-relative Lengths

容器变宽和变窄时怎样计算

下面用两个容器宽度说明计算过程,并作如下假设:

  • 根元素的 font-size16px,所以 8rem = 128px
  • Grid 容器宽度是确定值;
  • 没有边框、内边距和 gap
  • 内容不影响轨道宽度。

容器宽 800px

1 : 3 分配:

左列 = 800px × 1 / 4 = 200px
右列 = 800px × 3 / 4 = 600px

左列算得 200px,没有触及 128px 的下限,因此两列宽度为 200px600px

容器宽 400px

先按 1 : 3 计算左列:

400px × 1 / 4 = 100px

100px 小于下限,因此左列取 128px,右列使用余下的宽度:

左列 = 128px
右列 = 400px - 128px = 272px

下限生效后,两列不再保持 25% : 75%

为什么右列写成 minmax(0, 3fr)

3fr 单独作为轨道尺寸时,Grid 使用 auto 作为下限,相当于 minmax(auto, 3fr)。这个下限可能取决于网格项(Grid item)的最小宽度,以及内容所需的最小宽度。CSS Grid Layout Module Level 2:Track Sizing Functions

例如,右列中有一段默认不能断行的长字符串:

<div class="row">
  <div class="label">Account</div>
  <div class="content">aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</div>
</div>

如果右列写成 3fr,长内容可能把这一列撑宽,超出容器余下的宽度:

.row {
  display: grid;
  grid-template-columns: 10rem 3fr;
}

改成 minmax(0, 3fr) 后,右列下限为 0,内容不再决定轨道的最小宽度:

.row {
  display: grid;
  grid-template-columns: 10rem minmax(0, 3fr);
}

这里变窄的是轨道。超出轨道的内容不会因此自动隐藏、滚动或换行,还需要在网格项上处理溢出:

/* 允许长字符串在必要时断开 */
.content {
  overflow-wrap: anywhere;
}
 
/* 或者保留内容宽度,让当前区域横向滚动 */
.content {
  overflow-x: auto;
}
 
/* 或者裁掉超出区域的部分 */
.content {
  overflow: hidden;
}

overflow-wrap: anywhere 提供的断行位置会计入 min-content 宽度,适合处理长 URL 或连续字符串。CSS Text Module Level 4:Overflow Wrapping

一个完整的标签与内容布局

<dl class="details">
  <div class="details__row">
    <dt>Username</dt>
    <dd>Youyou</dd>
  </div>
  <div class="details__row">
    <dt>Repository</dt>
    <dd>an-extremely-long-repository-name-without-a-normal-break-point</dd>
  </div>
</dl>
.details {
  margin: 0;
}
 
.details__row {
  display: grid;
  grid-template-columns:
    minmax(8rem, 1fr)
    minmax(0, 3fr);
  gap: 1rem;
}
 
.details__row > dt,
.details__row > dd {
  margin: 0;
}
 
.details__row > dd {
  overflow-wrap: anywhere;
}

gap: 1rem 先占用两列之间的宽度,其余部分再按 1 : 3 分配。左列最窄为 8rem,右列可以变窄,长字符串由 overflow-wrap 断行。

与几种相似写法的边界

写法列宽行为
25% 75%两列按容器宽度的百分比计算,没有 8rem 下限;加上 gap 后,总宽度可能超过容器。
8rem 3fr左列固定为 8rem;右列占用余下空间,其 auto 下限可能受内容影响。
minmax(8rem, 1fr) 3fr左列有 8rem 下限,并按比例变宽;右列带有 auto 下限。
minmax(8rem, 1fr) minmax(0, 3fr)左列有 8rem 下限,两列按 1 : 3 分配余下空间,右列下限为 0

这组声明只控制轨道宽度,不会把两列自动改成上下排列。需要改变排列方式时,还要使用 media query 或 container query。