我们来聊聊网页中控制文本换行(或不换行)的各种方法。CSS 为我们提供了大量工具,确保文本按预期方式排布,此外我们还会介绍一些使用 HTML 和特殊字符实现的技巧
保护布局
通常,文本流会在“软换行机会”换到下一行,这是一个形象的名字,代表你想要文本自然中断的位置,比如单词之间的空格或者单词后面的连字符,但是有时你会发现长文本没有“软换行机会”,例如非常长的单词或者 URLs,这会造成各种布局问题,例如文本溢出容器,或迫使容器宽度过大,导致其他元素错位
提前预判文本不换行可能引发的问题,是良好的防御性编码习惯。幸运的是,CSS 为我们提供了一些解决这类问题的工具
溢出文本换行
给元素设置overflow-wrap: break-word,可允许文本在需要时从单词中间换行。该属性会先尝试把单词完整移动到下一行,如果下一行杠仍没有足够的空间,才会从单词中间截断换行
还有overflow-wrap: anywhere,它同样会截断单词,但两者区别在于对元素min-content计算的影响——当设置元素width: min-content时,这种差异将 非常明显
.top {
width: min-content;
overflow-wrap: break-word;
}
.bottom {
width: min-content;
overflow-wrap: anywhere;
}
当顶部元素设置overflow-wrap: break-word时,计算min-content尺寸会按单词未截断的文本长度,因此其宽度等于最长单词的宽度。底部元素设置overflow-wrap: anywhere时,计算min-content尺寸会考虑所有可能截断的位置——由于任何位置都可截断,因此min-content宽度可缩小到单个字符的宽度
请记住,只有当涉及min-content计算的时才会出现这种差异,如果我们将width设为某个固定值,overflow-wrap: break-word和overflow-wrap: anywhere的效果是一样的
直接截断单词
另一种断词方式是word-break: break-all,这种方式甚至不会保持单词的完整性——而是直接截断单词,我们来看示例
请注意,长单词不会像使用overflow-wrap时那样先移动到下一行;同时还要注意,即使下一行有足够的空间,它依然会被截断
word-break: break-all截断单词时毫无压力,但对标点符号会谨慎处理。例如,若句子结尾是句号,word-break: break-all会避免句号出现在新行开头。如果你无论如何都要换行,即便遇到标点符号——可以使用line-break: anywhere
注意看word-break: break-all是如何将“k”移动到下一行,以避免新行开头出现“.”,而line-break: anywhere则不介意句号出现在新行开头
长标点符号序列
如果遇到连续的长标点序列,我们来看看之前提到的 CSS 属性,如何处理
overflow-wrap: break-word和line-break: anywhere都能保证文本在容器内,但是word-break: break-all这次又在标点上处理异常——导致文本溢出容器
请记住,如果你完全不希望文本溢出,要注意word-break: break-all无法阻止标点失控导致的溢出问题
指定断词位置
若想更准确控制断词位置,你可以在文本中手动插入<wbr>来设置断词点,你也可以使用长度为零的空格——&ZeroWidthSpace,HTML 实体(是的,必须像你看到的这样全部大写)
下面我们用一段正常情况下不会换行的长 URL 来演示,看看插入上述元素后的效果
<!-- normal -->
<p>https://subdomain.somewhere.co.uk</p>
<!-- <wbr> -->
<p>https://subdomain<wbr>.somewhere<wbr>.co<wbr>.uk</p>
<!-- ​ -->
<p>https://subdomain​.somewhere​.co​.uk</p>
自动连字符
通过设置 CSS 属性hyphens: auto,可让浏览器在合适的位置截断单词并添加连字符。连字符规则由语言决定,因此你需要告诉浏览器当前使用的语言——可以通过在 HTML 中设置lang属性实现,既可以直接设置在相关元素上,也可以设置在<html>标签上
<p lang="en">This is just a bit of arbitrary text to show hyphenation in action.</p>
p {
-webkit-hyphens: auto; /* for Safari */
hyphens: auto;
}
手动连字符
你也可以手动控制,通过 HTML 实体­插入“软连字符(soft hyphen)”。该实体在页面中默认不可见,只有浏览器决定在该位置换行时,才会显示出连字符。注意下面的例子,我们使用了两次­,但只在文本换行的位置显示出一个连字符
<p lang="en">Magic? Abraca­dabra? Abraca­dabra!</p>
当使用­时,若想在换行时显示正确,hyphens属性必须设置为auto或者manual;方便的是hyphens的默认值是manual,因此你不需要额外添加 CSS(除非因为某些原因声明了hyphens: none)
防止文本换行
现在我们换个方向,有时候你可能不希望文本自动换行,以便更好控制内容的展示方式。这里有几种方法可以帮你实现
首先是white-space: nowrap。在元素上设置该属性,禁止文本自动换行
预格式化文本
还有white-space: pre,它会完全按照你在 HTML 中书输入的格式换行,但要注意,它会保留 HTML 中的空格,因此需注意你的文本格式,你也可以使用标签<pre>实现同样的效果(<pre>已经默认设置white-space: pre)
<!-- the formatting of this HTML results in extra whitespace! -->
<p>
What's worse, ignorance or apathy?
I don't know and I don't care.
</p>
<!-- tighter formatting that "hugs" the text -->
<p>What's worse, ignorance or apathy?
I don't know and I don't care.</p>
<!-- same as above, but using <pre> -->
<pre>What's worse, ignorance or apathy?
I don't know and I don't care.</pre>
p {
white-space: pre;
}
pre {
/* <pre> sets font-family: monospace, but we can undo that */
font-family: inherit;
}
等等,单词不能截断?
当元素设置了white-space: nowrap或white-space: pre时,你仍可以在元素内使用<br>标签换行
但是如果在元素内使用<wbr>呢?很有意思的问题——因为不同浏览器表现不一致。Chrome/Edge 会识别<wbr>并可能触发换行,而Firefox/Safari 则不会
不过对于零宽度空格(​),所有浏览器的表现倒是一致,当元素设置了white-space: nowrap或white-space: pre时,都不会触发换行
<p>Darth Vader: Nooooooooooooo<br>oooo!</p>
<p>Darth Vader: Nooooooooooooo<wbr>oooo!</p>
<p>Darth Vader: Nooooooooooooo​oooo!</p>
不换行的空格
有时你可能希望文本自动换行,但某些位置不换行。好消息!有一些特定 HTML 实体可以帮你实现这种需求
“不换行空格( )常用来保留单词间的空格,同时禁止单词在该空格处换行
<p>Something I've noticed is designers don't seem to like orphans.</p>
<p>Something I've noticed is designers don't seem to like orphans.</p>
单词连接符和不换行连字符
即使文本中没有空格,也可能发生自动换行,比如在连字符(-)之后,若想在不添加空格的时候禁止这种换行,你可以使用⁠(大小写敏感)生成一个“单词连接符”。使连字符前后的单词保持为一个整体,你还可以使用“不换行连字符”‑(它没有一个 HTML 实体名字)
<p>Turn right here to get on I-85.</p>
<p>Turn right here to get on I-⁠85.</p>
<p>Turn right here to get on I‑85.</p>
CJK 文本和换行
CJK(Chinese/Japanese/Korean)文本在某些方面表现与非 CJK 文本不同。可用于额外控制 CJK 文本换行
浏览器默认允许 CJK 文本在单词内换行,这意味着word-break: normal(默认)和word-break: break-all的效果是一致。不过,你可以使用word-break: keep-all禁止 CJK 文字在单词内换行(非 CJK 文本不受影响)
下面是一段韩语示例。注意“자랑스럽게”这个单词是否会换行
注意,中文和日文不像韩文那样在单词间使用空格,因此若处理不当,word-break: keep-all很容易导致长文本溢出
CJK 文本和行内换行规则
我们之前讨论过,非 CJK 文本设置line-break: anywhere后,可在标点处换行且表现正常——这一点对 CJK 文本同样适用
下面有一个日文的例子,注意“。”是否被允许出现在新行开头
line-break还有其他值会影响 CJK 换行的值,例如:loose、normal、和strict。这些值会指导浏览器在决定插入换行的位置时,使用哪种规则。W3C 规定了部分规则,浏览器也可能添加自己的规则
值得一提的元素溢出
CSS 属性overflow并不是专门用于文本的,但它常被用来确保当元素的宽度或高度受到限制时,文本不会渲染在元素之外。
.top {
white-space: nowrap;
overflow: auto;
}
.bottom {
white-space: nowrap;
overflow: hidden;
}
就像你看到的那样,当值为auto的时候,内容可滚动(auto: 仅在需要时候显示滚动条,scroll:始终显示滚动条)。设为hidden时:截断溢出的内容,不再显示
overflow是overflow-x和overflow-y的简写形式,分别代表水平方向和竖直方向的溢出控制。你可以根据需求选择最合适的方式
我们可以在overflow: hidden的基础上添加text-overflow: ellipsis属性。此时溢出的文本仍会被截断,但会显示漂亮的省略号
p {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
额外技巧:伪元素换行
借助伪元素,你可以强制在行内元素的前后面插入换行,同时保持该元素的行内属性
首先,设置::before或::after的content值为'\A',然后设置white-space: pre,确保该换行符能被浏览器识别
<p>Things that go <span>bump</span> in the night.</p>
span {
background-color: #000;
}
span::before, span::after {
content: '\A';
white-space: pre;
}
我们也可以给<span>设置display: block达到同样的换行效果,但这样一来它就不再是行内元素。而通过伪元素方法,background-color能让我们清晰看到它还是行内元素
额外的说明
- 在之前有一个旧的 CSS 属性
word-wrap。它不是标准属性,现在浏览器把它看作是overflow-wrap的别名 white-space还有一些值我们没有提到,pre-wrap、pre-line、和break-spaces。与我们之前提到的不同,这些值不阻止文本换行- CSS4 定义了一个
text-wrap属性,看起来很实用,但在撰写本文时,尚无浏览器支持它
是时候结束了
网页中文本流排布涉及很多细节,大多数时候你无需过多关注,因为浏览器会自动处理,但当你确实需要对显示效果进行更多控制时,知道自己有多种选择,会很有帮助