h1 上下那圈「幽灵空白」,你调了三年 padding 都没消掉——其实它一直在字体的度量值里
正文:
先把结论说在前面:标题上下那圈怎么调都调不干净的空白,不是 margin、不是 padding、也不是行高写错了,而是字体本身自带的 ascent / descent 度量被浏览器原样算进了行盒。你每加 1px padding,它就跟着长 1px,永远差那么一点。CSS 早就给了工具——text-box-trim 和 text-box-edge(早期叫 leading-trim / text-edge),今天可以在 Chrome 149+ 和 Safari 26+ 上直接用了。
为什么你调不出「刚刚好」
CSS 里一行文字的高度,等于字体度量里 ascent + descent 那一整块,再加上行高多出来的 leading,一半放上、一半放下。问题在于:这个 ascent 是字体的「推荐上边界」,不是字母的实际上边界——大写字母 H 的顶部、小写 x 的高度(x-height)、基线,都在这个盒子的更里面。
结果就是,你写 h1 { padding: 0 },标题上方依然有一圈空白;你想让标题跟下面正文刚好隔 24px,得靠 margin-top: -8px 这种数值去「猜」。换个字体,原来那套负 margin 全部作废。这就是「幽灵像素」——肉眼看得见,CSS 里找不到。
标准给的解法:把盒子的边收进字形
两个属性配合用,就能让文字盒子的上下边真正贴到字形上:
text-box-trim: trim-both—— 告诉浏览器,把上下两侧多余的 leading / 度量空白剪掉text-box-edge: cap alphabetic—— 定义「上下边对齐到哪里」:上边对齐大写字母高度(cap height),下边对齐基线(alphabetic)
.tight-h1 {
text-box-trim: trim-both;
text-box-edge: cap alphabetic;
font-size: 2.5rem;
line-height: 1.2;
padding: 0;
/* 现在这个盒子的上下边,就是大写字母的上下边 */
}
收完之后,标题上方那圈空白就没了——你设的 margin: 24px 就是肉眼看到的 24px,不用再减掉一个神秘偏移。
生产环境怎么用才不翻车
这是「渐进增强」的典型场景,不要一把梭:
/* 默认:老浏览器还是老样子 */
.heading-l { margin-block-start: 1.5rem; }
/* 支持才收边,顺带把间距 token 换成整数 */
@supports (text-box-trim: trim-both) {
.heading-l {
text-box-trim: trim-both;
text-box-edge: cap alphabetic;
margin-block-start: 1rem;
}
}
几个必须记住的坑:
- 它不是
line-height: 0。收边剪的是盒子几何,不是行间距。多行文本的可读性靠line-height保着,别为了对齐把它压到 1 以下。 - 字体回退会变。你按主字体量好的 cap height 收边,一旦主字体加载失败、回退到系统字体,度量就换了,对齐会轻微偏移。用
font-display+size-adjust稳一下回退字体,是这套方案的好搭档。 - 中英混排要实测。CJK 字形的 cap height / 基线跟拉丁字母不是一套,
text-box-edge在中文标题上收出来的效果和英文标题不一样,别只看英文 demo 就上线。 @supports一定要包。否则不支持的环境里,间距 token 直接塌掉。
下一步
打开你现在最头疼的那个标题组件,把 margin / padding / 负 margin 全删掉,加 @supports (text-box-trim: trim-both) 试一段。如果它终于和大写字母贴住了,恭喜——你以后给设计系统写间距 token 时,可以按「真实留白」来定,而不是按「扣掉幽灵像素之后剩多少」来凑。这就是从「让它看起来对」到「让它几何上真的对」的那一步。
评论区
登录后可评论。