卡片不知道自己被放在哪里
一句话总结
flex 划分一条轴,grid 同时掌控两条轴。而且响应式真正的问题不是“屏幕有多少 px”,而是“这个盒子有多少 px”,回答这个问题的就是容器查询。
为什么需要它
做一个卡片组件。为了让它在窄的时候纵向排列、宽的时候横向排列,使用了媒体查询。
.card { display: block; }
@media (min-width: 700px) { .card { display: flex; } }
把这张卡片放在正文里,运行得很好。可是把同一张卡片放进窄的侧边栏,就坏了。屏幕是 1400px,所以媒体查询为真,而卡片所在的盒子是 280px。
组件不知道自己被放在了哪里。 媒体查询只询问屏幕宽度,而组件需要知道的是自己盒子的宽度。这种错位,是设计系统中最常出现的结构性问题。
工作原理——flex 与 grid
该用哪一个,不取决于喜好,而取决于维度。MDN 的对比文档 整理了这种关系。
| 情况 | 适合的选择 |
|---|---|
| 在一行(或一列)之内划分 | flex |
| 根据内容大小自动分配 | flex |
| 行和列都要确定 | grid |
| 页面骨架——页眉、侧栏、正文、页脚 | grid |
| 即使数量变化,也必须守住位置 | grid |
flex 从内容出发。 由项目自己决定大小,然后分配多出来或不足的部分。grid 从框架出发。 先确定格子,再把项目放进格子。
所以像工具栏或按钮行这样“不知道里面会放什么的一行”适合 flex,而像页面骨架这样“格子是先确定的”则适合 grid。
flex 项目不缩小的问题
在 flex 中最常遇到一个陷阱。放进了长文本或宽表格的项目,不会缩小,反而把容器顶出去。
原因是 flex 项目的 min-width 默认值是 auto。这个值的意思是“不会比内容的最小尺寸更小”,所以只要有无法换行的长字符串,就必须占据那么多。MDN 的 flex 比例文档 解释了这个行为。
.item { min-width: 0; } /* 또는 overflow: hidden */
该代码块中的韩文注释的意思是:也可以用 overflow: hidden。
这一行,在很多情况下就是“为什么会出现横向滚动条”的答案。
flex: 1 是 flex-grow: 1; flex-shrink: 1; flex-basis: 0% 的简写。因为 flex-basis: 0%,所以会忽略内容大小,把剩余空间平均分配。flex: auto 的 flex-basis: auto,所以从内容大小出发。把这两者混用,会得到与预期不同的结果。
用 grid 布局而无需计算列数
grid 有一种无需媒体查询就能自动响应的惯用写法。
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 16px;
}
它的意思是“创建最小为 240px 的列,能放几列就放几列,剩余的空间平均分配”。即使不去计算屏幕宽度,列数也会自动改变。CSS Grid 2 定义了 auto-fit 与 auto-fill 的区别——auto-fill 会保留空列,而 auto-fit 会折叠空列,剩下的项目就会拉伸。
容器查询
但仍然有遗留的问题。上面的惯用写法改变的是列数,却改变不了卡片内部的布局。如果卡片在 240px 时和 600px 时,内部结构需要不同,那么卡片就必须询问自己的宽度。
.card-wrap { container-type: inline-size; }
@container (min-width: 420px) {
.card { display: grid; grid-template-columns: 120px 1fr; }
}
container-type: inline-size 是声明“要把这个元素的行内方向尺寸作为查询对象”。这样它的后代就可以用 @container 来查询祖先的宽度。它由 CSS Containment 3 定义,MDN 的容器查询文档 整理了用法。
要记住两点。
- 它无法查询自己。 规则要加在声明为容器的元素内部的元素上,而不是这个容器元素本身。所以需要多包一层包装元素
container-type: inline-size会启用尺寸包含(size containment)。在该方向上,子元素不再影响父元素的大小,所以在原本让高度贴合内容的地方,可能会出现意料之外的结果
在现场相遇的样子
这是实际遇到过的三种情况。
第一,同一个组件在两个位置以不同的方式坏掉。 在正文里完好,只在侧边栏里坏掉。这几乎总是本该使用媒体查询的位置,实际上需要的是容器查询。
第二,出现了横向滚动条,却找不到元凶。 最常见的是 flex 项目的 min-width: auto,其次是 width: 100vw(它包含滚动条的宽度)。
第三,媒体查询的断点超过了十个。 这通常是一个信号,说明组件本来没有理由去询问屏幕宽度,却在询问。改用 repeat(auto-fit, minmax(...)) 和容器查询之后,大部分断点都会消失。
下一项测验要确认什么
会确认 flex 与 grid 的取舍、flex: 1 与 flex: auto 的区别、min-width: auto 造成的溢出、auto-fit 与 auto-fill 的区别,以及容器查询与媒体查询有什么不同。