12k
All articles

如何修复 CSS 中文字上下多余的空白

使用 CSS text-box-trim 消除文字上下的多余空白,了解字体度量和 line-height 如何产生间隙、如何选择 text-box-edge,并为旧版浏览器添加回退方案。

OpenReplay Team
OpenReplay Team
如何修复 CSS 中文字上下多余的空白

文字上下多余的空白有两个来源:一是字体内置的垂直度量(vertical metrics),二是 line-height 在行的上下两侧各自增加(或减少)的半行距(half-leading)。CSS 原生的解决方案是 text-box: trim-both cap alphabetic。

你设置了 line-height: 1.1,去掉了所有外边距,可标题所在的盒子还是比文字本身高。于是你加了一个负外边距,反复调整到看起来合适为止,然后就不再管它。直到品牌字体换了。

本文将介绍这些空白从何而来、为什么负外边距无法可靠地消除它们,以及如何使用 text-box-trim、text-box-edge 和 text-box 简写属性来裁剪标题和按钮上的空白。文末还提供了针对旧版浏览器的降级方案。

核心要点

  • 文字周围的多余空白来自两部分:字体为其最高和最深的字形预留的垂直空间,以及 line-height 增加或减少的行距。CSS 会将这部分行距平均分配到每一行的上方和下方。
  • text-box-trim 决定裁剪哪些边缘(none、trim-start、trim-end、trim-both),text-box-edge 决定每条边缘裁剪到哪条参考线。
  • text-box: trim-both cap alphabetic 将顶部裁剪到大写字母高度(cap height),将底部裁剪到基线,这与大多数设计稿测量间距的方式一致。
  • 只有当 text-box-trim 的值不是 none 时,text-box-edge 才会生效。
  • text-box-trim 不可继承,因此需要在每个要裁剪的元素上单独声明,而不是声明在 body 上。

症状:内边距无法解释的空隙

一个内边距为零、外边距为零、line-height 也设得很紧的标题,其大写字母上方和基线下方仍然留有空白。DevTools 显示内容框(content box)比可见的文字更高,而样式表中没有任何规则能解释这一差异。

常见的补救办法是反复试出一个负外边距:

h1 {
  line-height: 1.1;
  /* Tuned by eye for one font; wrong for any other */
  margin-block: -0.2em -0.15em;
}

这些数值只是示意用的占位值。它们并非由任何依据推导而来,而这恰恰是问题所在。

为什么文字上下会有多余的空白?

文字上下的多余空白有两个成因:字体度量和半行距。两者都属于文字本身,而非盒模型。

首先,字体会为其所有字形预留垂直空间,从高大的大写字母到 g、y 这类字母的下伸部(descender)都包括在内。正如 MDN 的 text-box-trim 参考文档所指出的,预留空间的大小因字体而异,因此同样的 font-size,行框(line box)的高度会随字体不同而有高有低。

其次,line-height 会增加行距(leading)。CSS 2.1 规范中关于行距与半行距的部分将行距定义为 line-height 与字体上行高度(ascent)加下行高度(descent)之和的差值。其中一半位于字形上方,另一半位于字形下方。许多字体的上行高度加下行高度大于 1.1em,因此在 line-height: 1.1 时,半行距往往很小,甚至为负值。但无论哪种情况,字体自身度量在大写字母上方和基线下方留出的空白依然存在。

每种字体预留的垂直空间都不相同,因此为某一种字体调好的负外边距放到另一种字体上就会出错。这也包括 Web 字体加载完成之前显示的后备字体——这一问题同样会导致与字体相关的布局偏移。如果你回放一段 Web 字体加载较晚的会话,就能看到字体切换的那一帧,以及使用”魔法数字”外边距的标题随之发生的跳动。

如何使用 text-box-trim 修复这个空隙?

text-box-trim 决定文本块的哪些边缘被裁剪,text-box-edge 决定每条边缘裁剪到哪条参考线。text-box-trim 接受四个关键字:

  • none:初始值,不进行任何裁剪。
  • trim-start:裁剪上(over,顶部)边缘。
  • trim-end:裁剪下(under,底部)边缘。
  • trim-both:同时裁剪两条边缘。

下面是同一个标题,用新属性替换了外边距的 hack 写法:

h1 {
  line-height: 1.1;
  text-box-trim: trim-both;
  text-box-edge: cap alphabetic;
}

应用 text-box-trim 后,盒子的顶部边缘与大写字母的顶端对齐,底部边缘则落在基线上。无论使用哪种字体,多余的空白都会消失,因为浏览器是从字体中读取这些位置,而不是依赖你的猜测。

text-box 简写属性可以在一条声明中同时设置这两个属性。以下两条规则是等价的:

h1 {
  text-box: trim-both cap alphabetic;
}

/* Same result: an omitted trim keyword means trim-both */
h1 {
  text-box: cap alphabetic;
}

哪些 text-box-edge 值最常用?

对于横排文本,cap alphabetic 和 ex alphabetic 这两组边缘组合几乎可以覆盖所有场景。text-box-edge 接受两个关键字:第一个设置上边缘,第二个设置下边缘。

关键字适用边缘裁剪到
text上边缘或下边缘字体的文本边缘(默认值 auto 即解析为此值)
cap仅上边缘大写字母的顶端
ex仅上边缘x 字高(x-height,即矮小写字母的顶端)
alphabetic仅下边缘字母基线(alphabetic baseline)

cap alphabetic 与设计师通常测量标题或标签周围间距的方式一致:从大写字母高度量到基线。当文本以小写字母为主、x 字高才是视觉上的顶部时,请使用 ex alphabetic:

.lowercase-label {
  text-box: trim-both ex alphabetic;
}

有三条规则最容易让人踩坑:

  • text-box-edge 单独使用时没有任何效果。除非 text-box-trim 的值不是 none,否则不会进行任何裁剪。
  • 在 text-box 简写中,省略裁剪关键字表示 trim-both,省略边缘关键字表示 auto。关键字 normal 等价于 none auto。
  • text-box-trim 和 text-box-edge 都不可继承。在 body 上设置它们并不会裁剪其中的标题或按钮。请在每个元素上单独声明,或者写在每个组件的基础样式中。

使用 text-box 让按钮文字居中

一个上下内边距相等的按钮,其文字常常看起来并不居中。内边距虽然相等,但字体在大写字母上方和基线下方的内部空白并不相等,这一差异在视觉上就表现为某一侧多出了内边距。

.button {
  padding: 0.75rem 1.25rem;
}

将文字的盒子裁剪到大写字母高度和基线后,文字与按钮边缘之间就只剩下内边距:

.button--trimmed {
  padding: 0.75rem 1.25rem;
  text-box: trim-both cap alphabetic;
}

以”Submit”这样的文字为例,现在大写字母上方的空间与基线下方的空间看起来一样了。CSS Inline Layout 规范允许 text-box-trim 作用于块容器、多列容器和行内盒,但各浏览器的实现有所不同。根据 Mozilla 的发布意向(intent to ship),Firefox 和 Safari 会裁剪行内盒,而 Chromium 不会。此外,裁剪对 flex 或 grid 容器无效,因此请为按钮设置 display: inline-block,或者将裁剪应用在按钮内部的文字元素上。由于底部边缘被裁剪到基线,g、p、y 等字母的下伸部会延伸到内边距区域内。这是符合排版预期的结果,并不是 bug。

浏览器支持与降级方案

MDN 将 text-box 标记为 Baseline 2026,自 2026 年 8 月起新近可用,并注明它可能无法在较旧的设备或浏览器中使用。不支持该属性的浏览器会忽略这条声明,并以正常的字体间距渲染文字。降级后的效果就是未裁剪的布局:不会出错,只是不够精确。如果设计依赖于旧有的补偿性外边距,可以借助 @supports 特性查询,仅在这些浏览器中保留它们:

h1 {
  text-box: trim-both cap alphabetic;
}

@supports not (text-box: trim-both cap alphabetic) {
  h1 {
    /* Old per-font patch, kept only where text-box is unsupported */
    margin-block: -0.2em -0.15em;
  }
}

总结

文字周围的空隙由字体度量加上半行距构成。负外边距只是把它掩盖起来,而且只对一种字体有效。请在每个标题和标签组件上用 text-box: trim-both cap alphabetic 替换这些外边距,将遗留的外边距移到 @supports not 中,并同时使用后备字体和 Web 字体检查最终效果。

常见问题

text-box-trim 会改变多行段落中的行间距吗?

不会。在块容器上,text-box-trim 只会影响第一行的顶部和最后一行的底部。换行之间的半行距保持不变,因此段落内部的间距仍由 line-height 控制。只有块的外侧边缘会向内收缩到大写字母高度和基线。

为什么不直接设置 line-height: 1 来消除多余的空白?

设置 line-height: 1 只会改变行距。字体自身在大写字母上方和基线下方预留的空间依然存在,所以盒子边缘仍然无法与大写字母高度或基线对齐。同时,它还会压缩换行之间的间距。text-box-trim 只根据字体度量裁剪外侧边缘,line-height 仍可自由地用于控制可读性。

使用 text-box 裁剪会影响周围元素的布局吗?

会。text-box-trim 会将元素的内容边缘向内收缩到所选的度量位置,因此元素在块方向上占用的空间会变小。外边距、flex 和 grid 的间隙(gap)以及相邻元素都将从裁剪后的边缘开始计算,而不是从字体预留空间的边缘开始。这就是为什么应用裁剪后间距令牌(spacing token)在视觉上会变得准确,也是为什么移除旧的负外边距很重要。

如何在 JavaScript 中检测是否支持 text-box?

调用 CSS.supports('text-box', 'trim-both cap alphabetic')。在接受该声明的浏览器中返回 true,在不接受的浏览器中返回 false。它执行的检测与 CSS 中的 @supports 规则相同,因此你可以在运行时据此决定是否应用补偿性外边距,或者记录用户实际使用的是哪种渲染路径。

Open-source session replay

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.