你以为页面孤字和段落寡妇只能靠设计师?今天 text-wrap 把这件事彻底原生化了
标题孤零零一个字、段落最后一行只剩一个词——设计师每次提我都要假装研究一下怎么修。这类问题存在了二十年,前端工程师的解法要么靠 JS 库、要么靠 <wbr> 硬断、要么干脆不管。今天 CSS 两行代码把它们彻底原生化了。
这个问题到底有多普遍
英文排版里有个专门的名词:「widow」,指的是段落最后一行只有一个单词的情况。「orphan」则指标题最后一行孤零零一个字。这两个词在排版行业里讨论了几十年,因为它们会让版面看起来像没做完——尤其是标题两行字数悬殊,第一行满满当当、第二行只有一个词,看起来像断了句。
在 CSS 出现之前,印刷工人靠手动调整字间距、行高、甚至在特定位置插入连字符来解决这个问题。浏览器时代,这个工作交给了 JS:流行库如 jQuery Widow Fix、Typist、Clamp.js,本质上都是在 DOM 渲染完之后再扫描一遍,找到孤字的位置,往回拉一个词,然后重新渲染。
代价你也知道:多一轮 layout 计算,响应式场景下每次 resize 都要重跑一遍,而且有时候会跟其他 JS 逻辑抢 DOM 导致闪动。
text-wrap: balance — 标题专用,字数均分
text-wrap: balance 是给标题设计的。浏览器不再采用「尽量塞满每行」的贪婪算法,而是把整个区块作为整体,用二分查找找到一个宽度,让所有行的字符数尽可能均衡。
h1, h2, h3, blockquote {
text-wrap: balance;
}
效果大约是这样:
- 默认:
Crafting Meaningful Digital Experiences for Every User→ 第一行很长,第二行只剩「User」 - balance 后:
Crafting Meaningful Digital Experiences / for Every User→ 两行字数相近,视觉对称
Chrome 对 balance 的计算做了限制:超过 6 行的内容,浏览器会静默忽略这个属性(Firefox 放宽到 10 行)。这是有意设计的——balance 算法复杂度是 O(n²),对于短标题这点计算量可以忽略不计,但对于长段落,如果真的去全局优化所有断点,渲染性能会明显下降。
这也意味着:text-wrap: balance 只适合标题、引言、卡片标题这类短文本区块。放在长段落上,浏览器会直接忽略它,不会有任何副作用。
text-wrap: pretty — 正文专用,防止寡妇
text-wrap: pretty 是给正文段落设计的。跟 balance 不同,它不重新计算所有行的断点,而是只盯着最后 4 行:提前预判孤字/寡妇的情况,把倒数第二行的断点往前挪一点,让最后一行至少有两个单词。
p, li, dd {
text-wrap: pretty;
}
pretty 还会优化以下情况:
- 段落末尾恰好是连字符后的短词(pull back 避免行末孤零零一个 hyphenated word)
- 行末标点(句号、逗号不应该在行首,但 greedy 算法有时候会把单字标点挤到下一行开头)
- 整体 ragged-right 的节奏感(每行右边缘略微起伏,比僵硬对齐更易读)
pretty 的计算成本是固定的——窗口大小恒定,不随段落长度增长——所以它可以用在任意长度的正文中而不用担心性能问题。
两者可以同时用
一页网站里,标题和正文都存在孤字/寡妇问题。最简洁的写法:
:root {
text-wrap: pretty; /* 全局正文 */
}
h1, h2, h3, h4, blockquote, figcaption {
text-wrap: balance; /* 标题覆盖为更严格的均衡 */
}
两行声明,覆盖整站。这是 progressive enhancement 最佳实践:任何不支持这两个值的浏览器会静默回退到普通换行,没有任何副作用。
浏览器支持现状(2026 年 9 月)
| 属性值 | Chrome | Firefox | Safari |
|---|---|---|---|
| balance | 114+ | 121+ | 17.5+ |
| pretty | 117+ | 134+ | 17.5+ |
覆盖率:balance ~96%,pretty ~88%(Firefox 134 才加入)。对于正文内容,建议加一个 @supports 渐进增强:
p {
text-wrap: pretty;
}
@supports (text-wrap: pretty) {
/* 只有支持 pretty 的浏览器才应用,没支持的就用普通换行 */
}
三个坑
坑一:balance 在 flex/grid 里可能跟 intrinsic sizing 打架。 标题放在 display: flex 容器里,如果容器靠标题内容的自然宽度撑开,balance 的宽度优化可能会让容器重新 shrink,反而导致标题比预期窄。解法:给标题父容器加 flex: 1 1 auto 或设置明确 max-width。
坑二:tabular numbers 不应该被 balance。 如果你的标题里有数字(日期、价格),balance 会让它们不再对齐。可以用 :has(> .tabular) 排除:
h1:not(:has(> .tabular)) {
text-wrap: balance;
}
坑三:pretty 在 Firefox 134 以前不存在。 如果你的正文受众里 Firefox 占比不低,孤字问题在 Firefox 用户那里依然会出现。这是 graceful degradation 的场景:回退到普通换行,用户体验略微降级,但内容依然可读。
下一步
下次设计师提「标题第二行只有一个词看着别扭」,先别急着搜 JS 库,先问自己一个问题:是标题还是正文?
- 标题 → 一行
text-wrap: balance - 正文 → 一行
text-wrap: pretty - 两个都有 → 两行全加上
如果项目里有现成的 Widowtamer、jQuery widow-fix 这类库,可以搜一下引用量,考虑逐步替换成这两行 CSS——体积减少几十 KB,而且不需要运行时计算。
排版的书设计师读了几十年,今天浏览器终于也在读了。
评论区
登录后可评论。