卡片最后一行「飘」到边框外——外层 min-height 与嵌套 grid button 的 Chromium 布局陷阱
侧边栏卡片列表,每张卡片底部都有一行「更新于 MM/DD HH:MM」的时间戳。某天用户反馈:这行文字漂浮在卡片边框下方的空白区域里,像是被甩出去了。
打开 F12,DOM 结构分明正确——时间戳 <span> 稳稳地在 <button> 里面。React 渲染没有问题。但视觉上,卡片的背景色和边框止步于徽章行,时间戳孤零零地悬在下一张卡片的上方。
现象
- 卡片:标题行 + 徽章行 + 时间戳行,共三层子元素。
- 标题和徽章显示在卡片边框内;时间戳行渲染在边框外面,飘在卡片间隙。
- F12 检查计算高度:外层
.record-item的height约等于min-height: 76px,远小于三行内容的自然高度(约 90–100px)。 - 把外层的
min-height: 76px在 DevTools 里临时删掉,时间戳立刻回到卡片内部。
根因
CSS 结构如下:
/* 外层卡片容器 */
.record-item {
display: grid;
grid-template-columns: minmax(0, 1fr);
align-items: start;
min-height: 76px; /* ← 触发点 */
border: 1px solid var(--line);
background: rgba(255, 255, 255, 0.55);
overflow: visible;
}
/* 内层可点击区域:唯一的 grid 子项,是 <button> */
.card-main {
display: grid;
min-height: 86px;
/* 三个 block 子元素:title / badge / meta */
}Chromium 在处理「外层 grid 容器 + 内部唯一子项是 <button> 且该 button 本身也是 grid」这一嵌套路径时,存在一个内禀尺寸计算的怪异行为:外层容器的 min-height 作为尺寸提示被传播进内层 <button> 的自布局过程,错误地封顶了 button 的渲染盒高度——尽管 button 本身既没有 height 也没有 max-height。
button 的渲染盒被限制在约 76px,但它的三个子元素自然高度叠加超过了这个值。CSS 没有裁剪(overflow: visible),最后一个子元素就被绘制到了 button 渲染盒之外——而 background 和 border 挂在外层的 .record-item 上,那里已经没有背景覆盖,时间戳因此「飘」了出去。
当结构是「外层 grid 容器 → 唯一子项是自身也是 grid 的 <button>」时,外层的 min-height 在 Chromium 内禀尺寸路径中会错误地成为内层 button 的渲染高度上限,导致 button 最后一个子元素溢出到 button 渲染盒之外。
<div role="button"> 或 <a> 在相同结构下不会复现这个 bug——这是 <button> 元素特有的 Chromium intrinsic-sizing 路径问题。
排查要点
在 F12 里做三步快速确认:
- 选中外层
.record-item,查看 computedheight:若接近min-height值而远小于内容自然高度,基本命中。 - 临时在 Styles 面板删除外层
min-height:时间戳行回位则确认是这个 bug。 - 查看 DOM:时间戳节点是否确实在
<button>内部(而非被浏览器 HTML 解析提升到 button 之外)——若 DOM 深度正确,排除「block 元素被提升」的同类假阳性。
正解
删除外层 grid 容器的 min-height,将高度保底逻辑移到内层 button:
/* AFTER — 修复后 */
.record-item {
display: grid;
grid-template-columns: minmax(0, 1fr);
align-items: start;
/* min-height 从这里移走 */
border: 1px solid var(--line);
background: rgba(255, 255, 255, 0.55);
overflow: visible;
}
.card-main {
display: grid;
min-height: 86px; /* 高度保底留在这里,空状态卡片不塌陷 */
}对应的 diff 极简:
.record-item {
display: grid;
grid-template-columns: minmax(0, 1fr);
align-items: start;
- min-height: 76px;
border: 1px solid var(--line);
background: rgba(255, 255, 255, 0.55);
overflow: visible;
}外层 grid 容器自身无高度约束,会跟随唯一子项(button)的实际高度撑开;button 的 min-height 保证空状态不塌陷。卡片背景和边框随着 grid 容器撑开,完整覆盖所有子行。
给内层 button 加 overflow: hidden 看起来会「消除」浮出的时间戳,但实际上是把它裁掉了——内容一多(多行标题)问题立刻复现,且更难排查。治根不治标。
一句话外卖
外层 grid 容器的
min-height是写给容器自己的,不要让它替代内层 button 做高度保底——否则 Chromium 会用它来意外封顶 button 的渲染盒,最后一行子元素就飞出去了。