字间距是错误的,我们无能为力……也许

Avatar of Juan Diego Rodríguez
Juan Diego Rodríguez

DigitalOcean 为您的旅程的每个阶段提供云产品。立即开始使用 200 美元的免费积分!

这篇文章是在我和 Emilio Cobos(Mozilla 的资深开发人员,也是 CSSWG 成员)谈论 最近的 CSSWG 会议 时产生的。我想知道他认为他们上次会议上讨论的最激动人心的主题是什么,而 2024 年充满了许多新的或即将出现的炫酷事物,例如砌体布局、if() 条件语句、锚点定位、视图过渡等等,我认为他的答案一定包含在其中。

他承认,我列出的亮点确实反映了社区的主流,尤其是从作者的角度来看。然而,令我惊讶的是,他最喜欢的讨论是一个完全不同的东西:letter-spacing 属性在不同浏览器上的渲染方式存在错误 这是一个在网络上根深蒂固的缺陷,浏览器多年来一直忽视 CSS 规范,由于缺乏更好的选择和兼容性问题,这个问题无法轻易解决。

Emilios 的答案是有道理的——他在 Gecko 上工作,渲染字体本身就是一门艺术。尽管如此,我仍然不明白问题究竟在哪里,为什么他觉得它如此有趣,甚至为什么它会存在,因为 letter-spacing 就像 CSS 一样古老。直到我深入研究 letter-spacing 的兔子洞时,我才了解到这个问题有多么复杂,我希望您也能像我一样对这个(并不那么)简单的属性感兴趣。

什么是字间距?

这个问题似乎很简单:字间距就是字母之间的间距。太棒了!这很容易,对人类来说。对于计算机来说,如何渲染字母之间的间距这个问题要复杂得多。人类只需在不加思索的情况下写下下一个字母。另一方面,计算机需要一种策略来渲染这个间距:它们应该在字母的开头添加整个间距,在结尾添加,还是将间距减半并在字母的两边添加?它是否应该与英语等从左到右 (LTR) 语言以及希伯来语等从右到左 (RTL) 语言不同?这些问题至关重要,因为选择其中一个作为标准会影响文本测量和换行在整个网络上的工作方式。

网络上使用了哪三种策略中的哪一种?取决于你问谁。CSS 规范中的实现与浏览器的实现完全不同,甚至浏览器渲染引擎之间也存在不兼容,例如 Gecko(Firefox)、Blink(Chrome、Brave、Opera 等)和 WebKit(Safari)。

CSS 规范怎么说

让我们稍微回溯一下,首先了解 规范 如何说明字间距应该如何工作。在撰写本文时,letter-spacing

指定印刷字符单位之间的额外间距。值可以为负数,但可能存在实现相关的限制。

正式规范中还有更多内容,但这足以让我们理解 CSS 规范希望 letter-spacing 如何表现。关键词是之间,这意味着字间距只应该影响字符之间的间距。我知道,听起来很明显。

因此,如规范给出的示例所示,以下 HTML

<p>a<span>bb</span>c</p>

…以及以下 CSS

p {
  letter-spacing: 1em;
}

span {
  letter-spacing: 2em;
}

…应该在两个“b”字母之间产生相等的间距

Letter spacing on paper. The letter spacing is only applied between the letters "b"s

但是,如果我们在任何浏览器(例如 Chrome、Firefox 或 Safari)上运行相同的代码,我们会发现间距并不局限于“b”字母之间,而是在整个单词的末尾也有间距。

Letter spacing on browsers. The letter spacing is applied between the letters "b"s and on the right-hand side of the last letter "b"

浏览器怎么做

我以为 letter-spacing 在字符末尾添加间距是正常的,并且不知道规范中说的是相反的。但是,如果你仔细想想,目前的做法确实有点不对劲……只是我们已经习惯了而已。

为什么浏览器在这方面不遵循规范呢?

正如我们之前所见,字间距对于计算机来说并不简单,因为它们必须遵循一种策略来确定间距的应用位置。在浏览器的情况下,标准一直是在每个字符的末尾添加一个单独的间距,而不考虑该间距是否超过整个单词。这可能不是最好的选择,但网络已经习惯了这种方式,现在改变它会导致网络上所有文本和布局发生各种变化。

这会在具有更大字间距的元素末尾留下一个间距,对于 LTR 文本来说是可以接受的,但它会在 RTL 书写模式下文本的开头留下一个空白。

这个问题在居中对齐的文本中更为明显,其中结尾的间距会将文本从元素的绝对中心推开。您可能至少一次遇到过需要在元素的另一侧添加padding 来弥补对文本应用的任何letter-spacing,例如在按钮上。

正如您所见,蓝色突出显示创建一个对称的锥体,而我们的文本却没有遵循。

更糟糕的是,“每个字符的末尾”对于不同的浏览器来说意味着不同的东西,尤其是在 RTL 书写模式下。Chrome 和 Safari(Blink/WebKit)表示字符的末尾始终在右侧。另一方面,Firefox(Gecko)在“阅读”末尾添加空格——在希伯来语和阿拉伯语中是左侧。亲自看看区别

Side-by-side comparison of letter spacing on Gecko and Blink/Webkit

可以修复吗?

第一个想到的想法是简单地遵循规范中的说明,修剪掉结尾字符处不需要的空格,但这种(反)解决方案会导致兼容性风险,这些风险实在是太大了,以至于根本不值得考虑;文本测量和换行会发生变化,可能会导致许多网站出现故障。 使用解决方法删除了额外空格的页面 可能是通过抵消元素的padding/margin 来实现的,这意味着改变目前的行为会使这些抵消变得无效或导致故障。

针对 letter-spacing,有两个真正的解决方案:重新设计空格在字符周围的分布方式,或者允许开发者选择我们想要将结尾空格放置在何处。

选项 1:重新设计空格分布

第一个选项是更改当前的 letter-spacing 定义,使其类似于以下内容

指定应用于每个印刷字符单位的额外间距,但不包括那些前进值为零的单位。额外间距在印刷字符单位的内联开始侧和结束侧之间平均分配。值可以为负数,但可能存在实现相关的限制。

简而言之,浏览器不再在字符末尾添加额外间距,而是将其平均分配到开头和结尾,从而产生对称的文本。这也会改变文本测量和换行,尽管程度较小。

Letter spacing with the symmetrical fix. The letter spacing is equally applied around the letters "b"s

现在居中对齐的文本正确地对齐到中心

Different examples of letter spacing being distributed between letters and achieving a symmetrical look

选项 2:允许开发者选择

即使偏移量减半,它仍然会导致布局发生变化,这对于一些人来说仍然是(合理的)不可接受的。这是一个难题:大多数页面需要,或者至少会从保持 letter-spacing 原样中受益,而新页面会享受对称的字间距。幸运的是,我们可以通过让开发者选择空格如何应用于字符来做到这两点。语法还待商榷,但我们可以有一个新属性来选择空格放置的位置

letter-spacing-justify: [ before | after | left | right | between | around];

每个值都代表应该添加空格的位置,并考虑到文本方向

  • before空格在字母的开头添加,遵循语言的方向。
  • after空格在字母的结尾添加,遵循语言的方向。
  • left空格在字母的左侧添加,忽略语言的方向。
  • right空格在字母的右侧添加,忽略语言的方向。
  • between空格添加到字符之间,遵循规范。
  • around: 字母周围的间距。

从逻辑上讲,当前的行为应该是默认不破坏任何东西,而letter-spacing将成为这两种属性(长度和放置)的简写。

letter-spacing: 1px before;
letter-spacing: 1px right;
letter-spacing: 1px around;

letter-spacing: 1px;
/* same as: */
letter-spacing: 1px before;

还有第三种选择吗?

当然,第三种选择是保持现状。我想说这不太可能,因为CSSWG 决定对这个问题采取行动,如果我不得不把口袋里的五分硬币押注的话,他们可能会选择第二个选项。

现在你知道了letter-spacing是坏的……我们不得不忍受它,至少现在是这样。但有一些选择可能会帮助在未来纠正这个问题。