> ## 文章索引
> 在这里获取全部文章的索引：https://lrd.im/blog/llms.txt
> 这是 lrd.im 的个人博客，记录设计、技术与思考。

# 探索 Web 网页内字符的换行规则

![封面图](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2021-03-14-56a375c11043/01.jpg)

前不久在验收公司项目内一个讨论区相关需求时，看到界面的样式出现这种问题，让从业界面设计两年多的我感到意外和不安。

![两张手机截图对比：左图为课程评论区，显示一长串连续数字和 URL 链接超出容器宽度溢出；右图为另一评论区，显示连续 Emoji 表情串超出容器宽度溢出，均为文本换行规则未设置导致的布局异常。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2021-03-14-56a375c11043/02.png)

是的，讨论区里的一长串数字、一段网址URL、一长串 Emoji 表情超出了容器宽度。讲道理开发应该不会没有设置宽度，因为左图第一段长文本也不会超出容器宽度。

这时我猜测是换行规则没设置好，才导致这种情况的发生。

为了搞清楚文本正常换行显示或溢出容器宽度的具体规则，我便在网上找了一些资料，也做了尝试。

这篇文章记录我对 Web 网页内换行规则的理解、实际应用中的解决方案，以及一个突破性新发现。

## 发现问题

网页内影响文字换行规则的 CSS 属性，常用的有两个：`word-break`和`overflow-wrap`。在讨论换行规则样式的设置之前，我们得先知道如果不主动设置这些换行样式，网页默认的换行的具体规则。

当`word-break: normal`和`overflow-wrap: normal`时，Chrome 浏览器默认的换行规则是：

1. 对于 [CJK 字符](https://zh.wikipedia.org/zh-hans/%E4%B8%AD%E6%97%A5%E9%9F%93%E7%B5%B1%E4%B8%80%E8%A1%A8%E6%84%8F%E6%96%87%E5%AD%97 "维基百科对 CJK 字符的介绍")，行内可用宽度不足以显示一个字符时，会另起一行展示。同时会遵循部分避头尾规则。
2. 对于西文单词[注1]，行内可用宽度不足以显示一个单词时，会另起一行展示。当单词宽度大于整个容器宽度时，文本将会超出容器，不强制换行。

注1：上面提到的「西文单词」，实际上指的是一段「连续文本」（网上没找到对应的定义，笔者给起的名字）。「连续文本」指的是一段中间没有换行符的文本。下面列举出我观察到的换行符/非换行符所包含的内容：

1. 换行符：指「-」、「?」、「空格」及CJK字符和Emoji。这些字符出现在西文中间且行内可用宽度不足时，浏览器会尝试在换行符号后换行。
2. 非换行符：指「/」、「!」、「@」、「.」、「꧂」等西文字符和文字乱码的符号。这些符号出现在一段西文中时，在浏览器也不会主动换行。换个说法是，这些符号与英文单词是一个整体。

2022-01-02 补充：「-」在两边都是数字时，不会被当做换行符。

![对比示意图：同一段含超长单词的英文句子在三种情况下的换行表现。普通句子超出容器不换行；添加换行符（连字符、问号等）后在符号处断行；添加非换行符（斜杠、感叹号等）后仍视为连续文本超出容器。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2021-03-14-56a375c11043/03.png)

所以，在默认情况下，一段比容器宽度还要长的连续文本，是会直接超出容器宽度的，也直接导致了文章开头出现的样式问题。

> 小知识1：在苹果系统中，按 「Option + 空格键」或「Shift + Option + 空格键」得到的 Unicode 为`&#160;`空白字符属于非换行符，出现在连续文本时也不会主动触发换行。而直接按键盘「空格键」得到的`&#32;`空白字符，则属于换行符。

> 小知识2：因为「-」属于换行符，所以界面设计时遇到显示时间区间的情况，要预留足够宽度或让开发强制换行，如下图：

![三个时间区间卡片对比示例：左两个卡片因「-」触发换行，日期分两行显示，标注为「合理」；右侧卡片宽度不足未换行导致日期被截断，标注为「避免」。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2021-03-14-56a375c11043/04.png)

## 寻找解决方案

上一章节发现了问题的根源，所以接下来要找到解决办法。理想情况下是不影响浏览器其他的默认换行规则，做到「**在连续文本到达容器边缘时，强制换行。不超出容器**」。

下面列出两个网页内常用的换行 CSS 属性值所对应的效果，有部分属性是不应该出现在网页内的，设计师们可以看看自己公司项目有没有踩到雷区。

[word-break](https://developer.mozilla.org/en-US/docs/Web/CSS/word-break "Mozilla 对 word-break 属性的介绍")：一段文本换行的规则。可用值：

| 值 | 官方定义 | 个人理解 |
|---|---|---|
| `normal` | 遵循浏览器默认换行规则 | — |
| `break‑all` | 对于 non-CJK（非中文/日文/韩文）文本，可在任意字符间断行 | 让西文没有了单词的概念，仅在到达容器宽度边缘时才会换行。必定会造成大量断开西文单词，且在部分浏览器下会导致 CJK 字符避头尾处理的效果失效。会造成易读性问题，谨慎使用 |
| `keep‑all` | CJK 文本不断行，Non-CJK 文本表现同 `normal` | 让汉字的换行规则跟西文默认的换行规则一样，都有单词的概念，在标点符号处才触发换行。会导致 CJK 字符避头尾处理的效果失效。 |
| `break‑word` | 效果与 `overflow-wrap: break-word` 一致，MDN 文档标明不建议使用（但当前用该属性的网站还挺多） | — |

![word-break 三种值的排版对比：normal 保持单词完整、中文正常换行，较长英文连续文本溢出容器边界；break-all 强制在容器边缘断开所有字符包括英文单词；keep-all 使中文失去换行灵活性，整词成块排列，较长英文连续文本溢出容器边界。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2021-03-14-56a375c11043/05.png)

`break-all`确实可以断开一段非常长的连续文本，但会一视同仁，强制断开所有单词，尽管下一行有足够的位置让该单词显示。而`keep-all`则会让中文排版难以阅读。这两种显然不是我们想要的效果。

由此可见，常规情况下我们不需要调整 word-break 的值， 盲目调整反而会造成其他问题。笔者在其他大型网站上就遇到过不少错误设置`word-break: break-all`，导致标点符号避头尾处理规则失效和强断单词的情况，看起来特别难受。

![对比截图：虎扑和网易新闻 Web 端错误使用 word-break: break-all 的排版问题。虎扑中英文混排时单词被强制截断，网易新闻中标点符号出现在行首，避头尾规则失效。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2021-03-14-56a375c11043/06.png)

> 不要再随意使用 word-break: break-all 了！

[overflow-wrap](https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-wrap "Mozilla 对 overflow-wrap 属性的介绍")：当单词无法完整放进行内时的换行规则，用于解决长单词换行问题。可用值：

| 值 | 官方定义 | 个人理解 |
|---|---|---|
| `normal` | 遵循浏览器默认换行规则 | — |
| `anywhere` | 为了防止溢出，如果行中没有其他可以接受的断点，则可能会在任何时候破坏本来就不会损坏的字符串（如长字或 URL）。此时文本的最小宽度是一个字符 | 一段比容器宽度还要长的连续文本，在到达容器边缘时会强制换行，最小宽度是一个字符的宽度 |
| `break‑word` | 与 `anywhere` 值的效果相同，区别是文本的最小宽度是一个单词 | 如果单词宽度比容器宽度还小，该单词就会超出容器 |

![overflow-wrap 三种值的对比示意图：normal 值下长链接溢出容器；anywhere 和 break-word 值均可在容器边缘强制断行，使长链接正常换行显示，两者视觉效果相同。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2021-03-14-56a375c11043/07.png)

可以看到，`anywhere`和`break-word`的换行效果是一样的，都能达到理想的效果，所以我们只要调整`overflow-wrap`属性的其中一个值，即可解决长连续文本超出容器宽度的问题。

至于是使用`anywhere`还是`break-word`，张鑫旭大神提供了[方案](https://www.zhangxinxu.com/wordpress/2020/03/css-overflow-wrap-anywhere/)，如果是 flex 布局则使用`anywhere`，常规布局则使用`break-word`。

对于文章第一张图右图的 Emoji 显示异常问题，笔者在多个设备上对比后，发现部分设备无法显示这些 Emoji 表情。

查询后发现 Unicode 对不上，乱码中「🙏」表情是`&#58397;`，而正常的「🙏」表情则是`&#55357;&#56911`，属于另一个课题，与本文主题无关。

![同一微信公众号评论区在 iOS、Chrome for macOS、Android 三端的对比截图：iOS 和 Android 正常显示「🙏」Emoji，而 Chrome for macOS 中该 Emoji 显示为乱码方块。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2021-03-14-56a375c11043/08.png)

## 新发现：例外的场景

### 发现了啥

在理清楚网页文字的换行规则后，我在思考一个点：

目前的常见的做法是当行尾有一段比较长的连续文本时，为了保证良好的易读性，该长文本需要另起一行，在行首开始展示，这时上一行的行尾就会缺了一块。同时也会让文章的右边出现间距不等的空白，视觉上会让人感觉不整齐。

![示意图：文本段落右侧出现不规则空白区域（红框标注），配文 "在什么场景下，才能充分利用这些空间呢"，说明行尾长单词换行后导致右侧留白参差不齐的视觉问题。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2021-03-14-56a375c11043/09.png)

> 有没有一种场景，可以名正言顺地直接从单词中间断开，甚至让标点符号出现在句首？

答案是：有！如果说我们是为了让文本有良好的易读性，我们才做出「尽量不在单词中间断开」以及「标点符号避头尾」的行为，那么如果一段文本是不需要保证良好的易读性（甚至说一段文本不是给人阅读的），就能打破这个限制。

> 什么文本不需要给人阅读？我想到了 **「网址/URL」**。

注：网址/URL不完全等于超链接，超链接是指从某个载体指向另一个目标的链接关系。有三种表现形式：
1. 网址/URL超链接：是一个站点、网页的完整路径，例如我的个人网站 [https://lrd.im](https://lrd.im)
2. 超链接：即用图片、按钮、特定文本等来指向某目标，如「点击跳转>」、「立即报名>」
3. 锚点/书签：在同一网页内，通常用来返回顶部或定位到某章节的元素。

![超链接三种表现形式的对比示意图：左为网址/URL 超链接（显示完整路径如 https://lrd.im）；中为按钮式超链接（显示「立即跳转›」）；右为锚点/书签（以「#一个标题」形式定位页内位置）。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2021-03-14-56a375c11043/10.png)

想想看，我们在下软件、素材的时候，网站上贴出来的网盘网址，都是直接点击跳转，没有细看网址上具体的英文、数字。由此我产生了一个比较激进的定义：

> 网址/URL 是用来产生交互的，例如：点击、长按、复制、运行等。不需要理解其文本内容的。

所以我认为网页中的网址/URL（通常是`<a>`标签），可以大胆利用`word-break: break-all`的特性，让网址不主动换行。最大限度地保证用户产生的文本能填满一行的宽度，文章右边完美对齐。

![四栏对比示意图：中英文混合内容中，仅对 \<a> 标签应用 word-break: break-all 后，网址/URL 会在容器边缘处断行，而不影响周围普通文本的换行规则，右侧对齐效果更整齐。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2021-03-14-56a375c11043/11.png)

如果按这个思路，我们甚至可以再进一步，使用`line-break: anywhere`同时无视掉 CJK 文本的标点符号避头尾规则…

![三栏对比图：左栏不作特殊处理，网址溢出容器边界；中栏对`<a>`标签用`word-break: break-all`，网址在容器边缘换行但标点仍受避头规则约束；右栏用`line-break: anywhere`，网址及 CJK 标点均无视换行规则，在容器边缘强制折行，对齐效果最佳。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2021-03-14-56a375c11043/12.png)

所以这种或许会适合用在社区贴文、聊天界面等用户生产内容、内容不受限制，且产生超链接前没有富文本编辑功能的场景。以及弹幕发言气泡这种需要极致压缩空间的场景。

> 谁在这样做？

有这种做法的产品还真不多，我目前只发现一个：**微信 iOS 版**。

![微信 iOS 与 Android 聊天界面对比：iOS 版中超链接、话题等蓝色可点击文本会无视换行规则、仅在容器边缘换行；Android 版则无此处理，换行表现普通。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2021-03-14-56a375c11043/13.png)

微信聊天时里如果识别到是**超链接**、**话题**或被**引用**，文本就会变成蓝色可点击状态。且文本换行规则会变为我上面所写的那样，无视任何规则，仅在容器边缘处换行。

而安卓版、电脑版及企业微信，则没有作特殊处理。

> 当然，我不认识微信内的设计师，所以不知道微信设计团队对网址做这种处理的出发点，有可能是其他原因，也说不定是个 BUG~

## 实际应用

基于对上述网页内文字换行规则的认知，我也在公司近期的一个需求内实践了一次。针对超链接做强制换行处理。从截图来看，应该是处理过后的效果会好些。

![公司项目实际应用对比图：左侧未作特殊处理，超链接直接新开一行展示；右侧使用 word-break: break-all 处理后，超链接紧贴着前方的文案，排版更紧凑整齐。](https://lrdim.oss-cn-shenzhen.aliyuncs.com/blogimg/2021-03-14-56a375c11043/14.png)

## 总结

这篇文章内容可能比以往要长，名词也比较多，很多地方也是我这几天研究到才发现。总结起来就是这四点：

1. 清楚知道 Chrome 浏览器的默认换行规则及换行符有哪些；
2. 在清楚知调整`word-break`值带来的正负面效果之前，不要轻易调整；
3. 使用`overflow-wrap`的`anywhere` / `break-word` 可以解决长连续文本超出容器宽度的问题；
4. UI 验收时记得测试超长连续文本的效果，避免有无意外情况发生；
5. 对于可点击跳转的网址，可以用`word-break: break-all`或`line-break: anywhere`来进行更极致地排版。

### 碎碎念

实际上在这次需求验收中发现的问题，调整换行规则只是其中一种解决方式。前端工程师们还有很多方法可以解决长连续文本超出容器宽度的问题。

但我作为设计师倒是利用了这次偶然的样式问题，了解一遍网页的各种换行规则、增加了一个 UI 验收时必查的一个点，以及一些意外的新发现，收获确实不少~


## 参考链接

- [The Type - 字谈字畅 144：CSS 中文排版的十年跬步](https://www.thetype.com/typechat/ep-144/)
- [word-break:break-all和word-wrap:break-word的区别 «  张鑫旭-鑫空间-鑫生活](https://www.zhangxinxu.com/wordpress/2015/11/diff-word-break-break-all-word-wrap-break-word/)
- [CSS line-break属性与中文标点换行 «  张鑫旭-鑫空间-鑫生活](https://www.zhangxinxu.com/wordpress/2021/02/css-line-break/)