Web Fonts 的优化:F-mods

发布于 大漠

前面花了三篇的篇幅(Web Fonts vs. 系统字体FOUT、FOIT 和 FOFTWeb Fonts 字体加载策略)围绕着 Web Fonts 的优化做了些探讨。但这些优化手段都还是会引起 Web 布局偏移。不过,CSS 的一些新特性,即 F-mods(字体度量覆盖描述符)来覆盖字体的一些度量参数,让系统字体字体(备用字体)和 Web Fonts 更接近,尽可能的减少布局偏移。

使用 F-mods 减少布局偏移

在了解如何使用 F-mods 减少布局偏移之前,我们有必要先花一点时间来简单的了解字体度量(Font Metrics)和 F-mods!

字体度量(Font Metrics)

正如 @Weston Thayer 在其博客《Intro to Font Metrics》开头提到:

字体文件包含大量关于字体的信息。

简单地说,字体是由一系列符号(通常称为字母或字符)组成的字体的数字表示形式。计算机读取字体文件,以便将屏幕上的字形渲染为像素。为了描述应该如何将所有这些单独的符号组合到单词、句子和段落中,字体设计人员用指标对字体进行编码。字体参数可以帮助计算机确定行与行之间的默认间距,上标(sup)和下标(sub)的高低,以及如何将两个不同大小的文本对齐。​

通常情况,这些参数不会向用户公开,但我们可以使用一些工具来获取这些参数,比如 FontForgeFont Inspector: ​

注意,上图中的 ascender、descender 和 lineGap 接下来就会聊到。

另外,字体在 Web 上的使用是个复杂的体系,除了涉及一些排版本知识之外,还涉及一些字体知识,这里就对字体度量及相关的 CSS 属性不做过多解读,如果你想深究这一领域,下面这些资源应该值得阅读:

F-mods 简介

F-mods 是 Font Metrics Override Descriptors(字体度量覆盖描述符)的简称。它对应着 CSS Fonts Module Level 4 规范中的 @font-face 部分的第十一小节,即 默认的字体度量覆盖。简单地说,就是新增的四个 CSS 属性,对应着字体度量的四个描述符:

  • ascent-override:对应字体度量中的 ascender 参数,用来覆盖分配给字体上升部分的尺寸,即上升指示(Ascent Metric);该描述符定义了字体基线(Baseline)以上的高度
  • descent-override:对应字体度量中的 descender 参数,用来覆盖分配给字体下降部分的尺寸,即下降指标(Descent Metric);该描述符定义了字体基线(Baseline)以下的高度
  • line-gap-override:对应字体度量中的 lineGag 参数,用来覆盖行间距,即行距指标(Line Gap Metric);该描述是字体推荐的行距或外部引线(External Leading)
  • advance-override:为每个字符设置一个额外的提前量,以帮助匹配行宽并防止单词溢出

特别声明:其中 advance-override 还未进入 W3C 规范,目前还仅处于提案中;而 ascent-overridedescent-overrideline-gap-override 属性目前已在 Chromium 87+ 和 Firefox 89+ 中实现!

F-mods 作用

这四个描述符的组合可以让我们告诉浏览器在下载 Web Fonts 之前字符占用多少空间,来覆盖回退字体(系统字体)的布局,使其与 Web Fonts 相匹配。简单地说:

这四个描述符可以让你的系统字体更接近 Web Fonts!

其中 ascent-overridedescent-overrideline-gap-override 描述符使我们 能够完全消除垂直布局的偏移,因为这三个描述符都会影响行高。

当计算行高时,字体的上升(Ascent)、下降(Descent)和行距(Line Gap)三指标会被设置为所用字体大小(font-size)的给定百分比,即解析为给定百分比(也就是ascent-overridedescent-overrideline-gap-override的值,它们取值是个百分比值,除默认值 normal之外)乘以字体大小(font-size)。这也让我们可以使用它们来覆盖行框高度(Line Box Height)和基线位置(Baseline Position):

行框高度(Line Box Height) = 上升(Ascent) + 下降(Descent) + 行距(Line Gap)
基线位置(Baseline Position) = 行框顶部(Line Box Top) + 行距(Line Gap) / 2 + 上升(Ascent)

注意,上面这几个专业术语,可以通过下图找到他们在一个字体中对应的位置:

假设,我们分别给 ascent-override 设置值为 80%descent-override 设置值为 20%line-gap-override 设置值为 0%,把这些参数套用到上面公式的计算公式中,就可以得出每个行框的高度为 1em(假设使用的font-size1em),而基线位于行框顶部以下 0.8em 的位置。

注意,开发者可以在元素上设置 line-height 为非 normal 的值。然而,line-height: normal 是一个重要的用例,我们希望在这种情况之下也能消除布局偏移。

advance-override 描述符允许我们减少水平布局的偏移,以及由不同的换行引起的垂直布局的偏移。这个描述为使用该字体的每个字符设置了一个额外的提前量。额外的提前量等于描述符的值乘以使用的字体大小。该描述符是在 CSS 的 letter-spacing 属性之外应用的。比如,如果我们在一个元素上设置了 font-size: 20px; letter-spacing: -1px,并且设置了 adavance-override: 0.1,那么最终会得到 20px * 0.1 - 1px = 1px 的字符间的额外间距。

如何获取 F-Mods 所需参数

字体对于很多 Web 开发者而言就是个迷,大多数情况只知道字体的名称,比如AlibabaSans102。字体对应的参数对于我们来说并不是开放的,而我们要使用 ascent-overridedescent-overrideline-gap-override (加上 advance-override,这里暂不深入讨论这个描述符,因为到写这篇为止,还没有任何浏览器支持这个描述符)就需要字体对应的 上升(Ascent)、下降(Descent)和 行距(Line Gap)几个度量参数的值。

你可能会问,这些参数要怎么获取?除了找字体设计师提供这些度量参数之外,Web 开发者还有另一途径,那就是使用一些在线工具,比如前面提到的 FontForgeFont Inspector 工具。它们可以帮助我们快速获取需要的参数值。这里拿 Font Inspector 来举例,因为它相对来说更简单些。只需要在控制面板上传你将使用的 Web Fonts 文件,建议上传字体的原始文件,即 .ttf.eot 格式。这个时候,你可以在 headhhea 中折叠面板中找到这些所需的参数值:

注意:这些描述符的值应该根据目标 Web Fonts 的头部(head)头部表(hhea)来获取。而且这些参数了会因系统不同有所差异,我们一般使用 hhea 中的参数值。

我们可以在 Font Inspector 的 hhead中找到四个关键值,它和这四个描述符有一定的映射关系:

  • ascender:对应 ascent-override
  • descender:对应 descent-override
  • lineGap:对应 line-gap-override
  • advanceWidthMax:对应 advance-override

除了这四个参数之外,每个字体还有一个 unitsPerEm 参数,其字面意思是字体的每一个Em的单位值,一般情况之下,unitsPerEm 的值为 1000(在 Font Inspector 检测工具中的 head 折叠面板中可获取)。

从规范中,我们可以获知ascent-overridedescent-overrideline-gap-override 描述符的值是百分比值(%),而从相关提案可以获知advance-override是一个小数值(能不能用百分比描述,待规范发布)。例如,如果 Web Fonts 的 unitsPerEm=1000ascender=1041,那么对应的 ascent-override 描述符值为 104.1%1041 / 1000 * 100% = 104.1%)。

如果已获得了这些参数,我们可以借助 CSS 自定义属性,让它们在实际使用的时候变得更灵活些:

@font-face {
    font-family: 'Arial'
    src: local('Arial');


    --unitsPerEm: 1000;
    --lineGap: 10;
    --descender: -237;
    --ascender: 1041;
    --advanceWidthMax: 815;


    ascent-override: calc(var(--ascender) / var(--unitsPerEm) * 100%);
    descent-override: calc(var(--descender) / var(--unitsPerEm) * 100%);
    line-gap-override: calc(var(--lineGap) / var(--unitsPerEm) * 100%);
    advance-override: calc(var(--advanceWidthMax) / var(--unitsPerEm));
}

如果你不习惯 Font Inspector工具,你还可以使用一款与其相似的工具 FontDrop,你只需要把要使用的 Web Fonts 拖到控制面板中,在 “Data” 一栏中可以获得该字体的所有信息,相应的在 hhea 一栏中能找到 "ascender"、"descender"、“lineGap” 和 “advanceWidthMax” 的值: ​

F-mods 的使用

有了这些字体度量参数之后,可知道了 F-mods 中每个属性对应的值是多少。这样一来,使用 F-mods 就容易地多了。假设你决定在使用 Web Fonts 时采用 F-mods 来减少字体带来的布局偏移,那么你只需要掌握这个基本诀窍即可: 在 @font-face 声明中使用 **src: local()**定义备用字体(系统字体)。这样就可以覆盖备用字体的显示,以匹配 Web Fonts:

/* Web Fonts */
@font-face {
    font-family: AlibabaSans102;
    src: url("https://example.com/AlibabaSans102.woff2");
    font-display: swap;
    font-weight: 700;
    font-style: normal;
}

/* 指定备用字体,local() 函数中指定备用的系统字体 */
@font-face {
    font-family: 'AlibabaSans102-fallback'
    src: local('Arial');

    /* 使用 Font Inspector 或 FontDrop 相关工具,获取 Web Fonts 映射 CSS 字体覆盖描述符所需参数值 */
    --unitsPerEm: 1000;
    --lineGap: 10;
    --descender: -237;
    --ascender: 1041;
    --advanceWidthMax: 815;

    /* 使用 CSS 自定义属性 和 calc() 函数将值转换成 CSS 属性相匹配的值 */
    ascent-override: calc(var(--ascender) / var(--unitsPerEm) * 100%);
    descent-override: calc(var(--descender) / var(--unitsPerEm) * 100%);
    line-gap-override: calc(var(--lineGap) / var(--unitsPerEm) * 100%);
    advance-override: calc(var(--advanceWidthMax) / var(--unitsPerEm));
}

.price {
    font-family: AlibabaSans102, 'AlibabaSans102-fallback'
}

简单地来看 ascent-overridedescent-overrideline-gap-override描述带来的作用。为了省事,下面示例中直接使用 local() 调用了系统的 Arial Bold 字体,并且分别使用这几个描述覆盖本地系统的 Arial Bold字体,创建新的字体。先来看 ascent-override

/* 使用 local() 调用系统的 “Arial bold” 字体,你可以在此更换成你喜欢的 Web Fonts */
@font-face {
    font-family: "Arial Bold";
    src: local(Arial Bold);
}

/* 使用 ascent-overrid 覆盖系统“Arial bold”字体,当作新字体 */
@font-face {
    font-family: "Arial-Bold-fallback";
    src: local(Arial Bold);
    ascent-override: 71%;
}

.default {
    font-family: "Arial Bold";
}

.adjusted {
    font-family: Arial-Bold-fallback
}

效果如下:

红字文字是未调整的(对应 .default,即使用的是 Arial Bold),其中大写的 AO 上面有空间(间距),而蓝色文本已使用 ascent-override调整过的(对应 .adjusted,即可使用了ascent-overrid 覆盖之后的 Arial-Bold-fallback )的字体,所以蓝字文本中的大写 AO 的上限高度与整个边界框是紧密相连的。

把上面示例中的 ascent-override换成 descent-override

/* 使用 descent-override 覆盖系统“Arial bold”字体,当作新字体 */
@font-face {
    font-family: "Arial-Bold-fallback";
    src: local(Arial Bold);
    descent-override: 0%;
}

效果如下:

从效果截图中不难发现,红色文本(未调整)的 DO 基线下有空间,而蓝色文本是调整之后的,它的字母紧贴在基线上。

最后看 line-gap-override描述符,把上面示例换成:

/* 使用 line-gap-override 覆盖系统“Arial bold”字体,当作新字体 */
@font-face {
    font-family: "Arial-Bold-fallback";
    src: local(Arial Bold);
    line-gap-override: 50%;
}

效果如下:

红色文本(未调整)没有行距覆盖,基本上是 0%,而蓝色文本已经被调整为 50%,在字母上方和下方相应的创建了空间。

如果我们把这覆盖都放在一起,你将看到的效果如下:

/* 使用 local() 调用系统的 “Arial bold” 字体,你可以在此更换成你喜欢的 Web Fonts */
@font-face {
    font-family: "Arial Bold";
    src: local(Arial Bold);
}

/* 使用 ascent-overrid 覆盖系统“Arial bold”字体,当作新字体 */
@font-face {
    font-family: "Arial-Bold-fallback";
    src: local(Arial Bold);
    ascent-override: 71%;
    line-gap-override: 50%;
    ascent-override: 0%;
}

.default {
    font-family: "Arial Bold";
}

.adjusted {
    font-family: Arial-Bold-fallback
}

最终效果如下:

我们来看一个真正的 Web Fonts 被 F-mods 覆盖后的效果,就拿AlibabaSans102 为例吧:

/* Web Fonts */
@font-face {
    font-family: AlibabaSans102;
    font-weight: 700;
    font-style: normal;
    font-display: swap;
    src: url("https://g.alicdn.com/eva-assets/8f07c38aa173457f747f15a8774161a4/0.0.1/tmp/font/0ce464d2-bb11-41c8-8470-0049cea5f6b1.woff2") format("woff2");
}

/* 系统 Arial bold */
@font-face {
    font-family: "Arial Bold";
    src: local(Arial Bold);
}

/* 覆盖系统 Arial bold*/
@font-face {
    font-family: Arial-Bold-fallback;
    src: local(Arial Bold);
    descent-override: 0%;
    ascent-override: 72%;
    line-gap-override: 3%;
}

/* 覆盖 Web Fonts */
@font-face {
    font-family: AlibabaSans102-fallback;
    src: local(Arial Bold);

    --unitsPerEm: 1000;
    --lineGap: 10;
    --descender: -237;
    --ascender: 1041;
    --advanceWidthMax: 815;

    ascent-override: calc(var(--ascender) / var(--unitsPerEm) * 100%);
    descent-override: calc(var(--descender) / var(--unitsPerEm) * 100%);
    line-gap-override: calc(var(--lineGap) / var(--unitsPerEm) * 100%);
    advance-override: calc(var(--advanceWidthMax) / var(--unitsPerEm));
}

最终效果如下: ​

@SimonHearne 在 Codepen 上提供了一个“加载你的 Web Fonts 和备用字体(系统字体),并且使用 F-mods 的示例”。可以查看到他们之间的差异: ​

当然,你也可以借助 CSS 自定义属性和一点 JavaScript 脚本,构建一个动态的 F-mods 使用案例。这里就不演示了,感兴趣的可以自己尝试一下。

F-mods 也不是万能的

F-mods 只是修正了垂直方向的间距和位置。这也意味着字符间距和字母间距仍然需要处理,否则你可能会在不同的点上断行,导致元素高度的改变,从而导致布局的偏移。不幸运的是,CSS 的 letter-spacinngword-spacing 并不能直接用于 @font-face 规则中,因此,我们需要在元素上单独使用。正如 @SimonHearne的示例中,就单独在一个选择器中使用了这两个属性:

/* Web Fonts */
@font-face {
    font-family: custom-font;
    src: url('https://simonhearne.com/assets/fonts/dosis-v17-latin-variable.woff2') format('woff2');
}

/* 使用F-mods调整后的备用字体 */
@font-face {
    font-family: fallback-font;
    ascent-override: 100%;
    descent-override: 20%;
    line-gap-override: normal;
    advance-override: 10;
    src: local(Arial);
}
    
/* 调整字母和单词之间间距 */
.fallback {
    letter-spacing: -1.1px;
    word-spacing: -0.2px;
}

这个工作稍微麻烦一些。如果你为了调整字母和单词之间间距,额外使用letter-spacingword-spacing属性,那么在 Web Fonts 加载成功之后,需要从你的样式表中删除这两个规则。这个时候你就需要使用 CSS Font Loading API 或 FontFaceObserver了,当然,你也可以直接使用 JavaScript 来控制,只是略为麻烦一些。

未来可用于 @font-face 新特性

正如 F-mods 中所描述的一样,ascent-overridedescent-overrideline-gap-override和已废弃的advance-override 都是用于 @font-face 规则中的 CSS 特性。他们都是用于服务 Web Fonts,主要目的 就是使用 CSS 来调整字体的度量参数,让备用字体(系统字体)更匹配 Web Fonts,从而减少 Web Fonts 引起的布局偏移。

值得大家庆幸的是,在 CSS Fonts Module Level 5 规范中又为 @font-face 规则添加了几个新属性。比如,用来覆盖字体上标(sup)和下标(sub)superscript-position-overridesubscript-position-overridesuperscript-size-overridesubscript-size-override 描述符。虽然这几个属性还没有得到任何浏览器的支持,但对于 Web 开发者而言,这是希望。

除此之外,还新增了 size-adjust 描述符,它允许我们调整字形的比例系数(百分比)。该描述符取代了前面提到的 advance-override描述符。正如 @Addy Osmani在他自己推特上,是这样描述 size-adjust

我们着重来了解一下 size-adjust 描述符。主要是因为,size-adjust 已经得到了 Chromium 92+ 和 Firefox 89+ 的支持

不过需要开启相关的功能。比如在Firefox浏览器地址栏输入about:config,然后搜索layout.css.size-adjust.enabled,并且将其设置为 true。重启浏览器之后你就可以在 Firefox 浏览器中看到 size-adjust描述符的效果了。

size-adjust

先来看一个有关于 size-adjust的视频:

上面的视频中,字体大小是一致的,都是64px,不同的是每个标题使用了不同的字体。视频中左侧图是没有使用 size-adjust的效果,文本会因为字体不同,对应的所在区域大小也不同。而右侧是使用了size-adjust的效果,确保了不同字体的最终尺寸都是 64px。从视频中可以明显看到,左侧因字体不同造成布局偏移,而右侧则没有。

那么,size-adjust 如何使用呢?其实,他的使用并不复杂,和前面介绍的 F-mods 使用差不多。比如下面这段 CSS:

@font-face {
    font-family: 'Lato';
    src: url('/static/fonts/Lato.woff2') format('woff2');
    font-weight: 400;
}

h1 {
    font-family: Lato, Arial, sans-serif;
}

我们现在要做的就是使用一个系统字体作为 Lato 的备用字体,比如 Arial,然后在相应的 @font-face 规则中使用 size-adjust描述符:

/* Web Fonts */
@font-face {
    font-family: 'Lato';
    src: url('./fonts/Lato.woff2') format('woff2');
    font-weight: 400;
}

/* 使用系统字体作为 Web Fonts 的备用字体 */
@font-face {
    font-family: "Lato-fallback";
    size-adjust: 97.38%;
    ascent-override: 99%;
    src: local("Arial");
}

h1 {
    font-family: Lato, Lato-fallback, sans-serif;
}

这意味着,使用了系统字体Arial(系统字体可以直接使用,不需要额外下载),然后使用 size-adjustascent-override 让系统字体 Arial 更接近 Lato 字体(Web Fonts),并且该 @font-face 创建的字体 Lato-fallback 作为 Lato 的备用字体。注意,经过调整的 Lato-fallback 更接近 Lato 字体。 也就是说,从备用字体 Lato-fallback 切换到 Lato(Web Fonts 字体加载完,会切换到 Lato字体,当然,这也和 font-display 的值有关),造成的布局偏移要好很多。

@Malte Ubl 创建了一个工具Automatic font matching,可以在给定两个字体和一个支持这些新特性(ascent-overridedescent-overrideline-gap-overridesize-adjust)的浏览器的情况下自动计算这些属性的值: ​

Automatic font matching 是基于 @Monica Dinculescu 的 Font Style Matcher构建的。详细的可以阅读 @Malte Ubl 的 《More than you ever wanted to know about font loading on the web》一文。如果你的 Web Fonts 不是 Google Fonts,而又希望 Automatic Font Matching 工具帮助你计算字体之间的差异,那么你可以把该工具的代码下载下来,在代码中更换你自己想要的 Web Fonts。

也就是说,我们在使用 Web Fonts 时,可以把这些描述符放置到一个 @font-face 规则中,并且该 @font-face 引用一个系统字体来作为 Web Fonts 的备用字体。这样一来,就可以改善 font-display: swap 引起的布局偏移。这样就有一个两全其美的效果:内容可以尽快被看到,而且还可以得到 Web Fonts 带来的美化,同时还不会牺牲用户看内容的时间

综合下来,我们在使用 Web Fonts 时,应该采用像下面这样使用,会给用户提供一个更好的体验,而且还能尽可能的改善 Web Fonts 带来的布局偏移(要完全根除 Web Fonts 带来的布局偏移是不可能的):

<head>
    <!-- 如果 Web Fonts 和域名在同一个地方,preconnect 可以忽略 -->
    <link rel="preconnect" href="https://g.alicdn.com" crossorigin />

    <!-- Web Fonts 预加载,需要放置在页面关键渲染资源之后 -->
    <link rel="preload" importance="high" href="https://g.alicdn.com/font/lota.woff2" as="font" type="font/woff2" crossorigin="anonymous">
    <style>
        /* 声明 Web Fonts */ 
        @font-face {
            font-display: swap;
            font-family: lota;
            font-style: normal;
            font-weight: normal;
            src: url('https://g.alicdn.com/font/lota.woff2') format('woff2');
        }

        /* 使用系统字体作为 Web Fonts 备用字体,并且使用 FontDrop 给的参数来覆盖 */
        @font-face {
            font-family: lota-fallback;
            font-style: normal;
            font-weight: normal;
            src: local('Arial'); /* 使用系统的 Arial 作为备用字体 */

            --unitsPerEm: 2000;
            --lineGap: 0;
            --descender: -426;
            --ascender: 1974;

            ascent-override: calc(var(--ascender) / var(--unitsPerEm) * 100%);
            descent-override: calc(var(--descender) / var(--unitsPerEm) * 100%);
            line-gap-override: calc(var(--lineGap) / var(--unitsPerEm) * 100%);

            size-adjust: 107.6%; /* 有可能需要手动调整 size-adjust 值*/
        }

        /* 运用 Web Fonts*/
        .font {
            font-family: lota, lota-fallback, sans-serif;
        }
    </style>
</head>

**特别声明:size-adjust 对应字体哪个参数,我也还没整明白,现在是手动调整这个属性的值。另外,size-adjust 不等同于 text-size-adjustfont-adjust-size 两属性,大家千万别混淆!**​

有关于这方面更详细的介绍,可以阅读下面几篇文章:

一些可用于排版的 CSS 特性

前面我们提到过,Web Fonts 可以设计成可变字体,可变字体除了让字体文件体积变小之外,还可以使用一些 CSS 的属性来控制 Web Fonts 在浏览器上渲染效果。除此之外,CSS 还提供了一些用于排版方面的新特性(不是用在 @font-face 规则内)。这里再花简短的篇幅来聊一下这些属性。

间距(Spacing)和字距(Kerning)

在字体文件中,有两种设置可以定义字符之间的空间。在 CSS 中可以使用 letter-spacingfont-kerning 来控制:

  • letter-spacing:用于设置文本字符的间距表现(每个字符左右两边的边距)
  • font-kerning:设置是否使用字体中存储的字距信息(两个字符之间的间距)

在字体中,间距(Spacing)是不能关闭的,否则渲染引擎(浏览器)在渲染文本时就不知道发何处理字符的间距;字距(Kerning)在浏览器中默认是关闭的,需要使用 CSS 的 font-kerning 来打开。

Kerning(字距)定义了字母的分布情况。对于良好地规定了字距的字体,字距特性使得字母分布更为统一,阅读体验更佳。

letter-spacing 大家非常熟悉,设置一个带有单位的正负值即可,比如 -.1pxfont-kerning 的使用也不复杂: ​ p { font-feature-settings: "kern" 1; font-kerning: normal; } ​

OpenType 字体的高级功能

OpenType 功能很强大,它们为控制字体开启了大量的可能性,而不必为获得相同的效果提供多个字体文件。

前面所说的可变字体就是属于 OpenType的,该字体所支持的功能都是由字体设计者决定的,而且 是所有的字体都支持相同的功能。在 CSS 中除了 font-variation-settings 属性用于控制可变字体之外,还可以使用 font-feature-settings 属性用来控制 OpenType 字体中的高级印刷功能。比如:

p {
    font-feature-settings: "onum" 1, "pnum" 1, "kern" 1, "ss01" 1;
}

示例代码中的 "onum"(old style figures)、"pnum"(proportional numerals)、"kern"(kerning) 和 "ss01"(stylistic sets) 代表着 OpenType 字体中不同的功能。有关于这些参数更详细的描述,可以阅读《font-feature-settings》: ​

Web开发者应该尽可能的使用类似 font-variant 这样的短标记属性或者相关的速记标识属性等, 类似 font-variant-ligatures, font-variant-caps, font-variant-east-asian, font-variant-alternates, font-variant-numericfont-variant-position。该属性是一个比较偏底层的功能接口,用于解决由于没有其他方法去访问和设置OpenType字体某些特性而无法解决一些特殊功能需求。特别需要注意的是,该CSS属性不应该用来开启大写字母转换。

字体平滑化

虽然字体文件中包含的提示信息在 MacOS 上大多数都被忽略了,但特定的浏览器对字体渲染提供了一些额外的控制。

html {
    -webkit-font-smoothing: antialiased; /* Chrome, Safari */
    -moz-osx-font-smoothing: grayscale; /* Firefox */
}

使用 font-smoothing 可以使 MacOS 和 iOS 上文本渲染更清楚、更细(Thin)。但这也可能导致渲染问题,特别是你已经使用了一种字更细(Thin)字体或font-weight(字重)antialiasedgrayscale 主要用于平衡在深色背景上使用浅色文字时的字体渲染。

注意,该属性已从 CSS 规范中移除,但在一些阅读类的网站上还是可能看到该属性: ​

优化可读性

我在 Medium 网站上看到 <body> 元素上设置了一个 text-renderng: optimizeLegibility 样式规则:

text-rendering定义浏览器渲染引擎如何渲染字体。浏览器会在速度、清晰度、几何精度之间进行权衡:

  • optimizeSpeed:浏览器在绘制文本时将着重考虑渲染速度,而不是易读性和几何精度。它会使字间距和连字无效。
  • optimizeLegibility:浏览器在绘制文本时将着重考虑易读性,而不是渲染速度和几何精度。它会使字间距和连字有效。该属性值在移动设备上会造成比较明显的性能问题
  • geometricPrecision:浏览器在绘制文本时将着重考虑几何精度, 而不是渲染速度和易读性。字体的某些方面—比如字间距—不再线性缩放,所以该值可以使使用某些字体的文本看起来不错。

如果你追求的是页面性能,那么 text-rendering 就慎用,甚至是不要使用。特别是在文本较多的地方。

字体性能的未来

很明显,字体性能还不是一个已经解决的问题。值得庆幸的是,在 W3C Web Fonts 工作组(Web Fonts Working Group)中,有一项工作正在进行,以改善这个问题。其中一项建议是渐进式字体丰富化,这是一个增量字体加载的概念,它允许浏览器准确地请求它们渲染文本所需的字符,有可能极大地减少初始字体文件的大小,从而提高渲染速度。

谷歌提供了一个关于这个增量传输概念的演示,如果你对这个概念感兴趣的话,可以深入阅读 @Jason Pamental的《Progressive Font Enrichment: reinventing web font performance》博文。

小结

布局偏移对用户体验不好,而且很难解决,特别是使用 Web Fonts 造成的布局偏移更难解决。虽然使用 font-display: optional 可以避免 Web Fonts 带来的布局偏移,但用户会在极短时间内看不到任何文本,甚至是字体加载完成也有可能看不到 Web Fonts 渲染的效果。而使用 font-display: swap 可以正常使用 Web Fonts 渲染文本,但会造成布局偏移。虽然 CSS 的新特性(ascent-overridedescent-overrideline-gap-overridesize-adjust)可以减少 Web Fonts 带来的布局偏移,但还是无法彻底根除。这似乎又进入到两难境地。或者说,就是和浏览器赛跑,即 试图在浏览器开始渲染文本之前加载完你的 Web Fonts

通过优化你的字体文件,与浏览器赛跑是可能的。

  • 使用 woff2 来最小化文件大小
  • 自己服务器托管 Web Fonts
  • 预加载关键字体(如果 Web Fonts 有多个,在一个主站上,使用 <link rel="preload"> 不超过三个)
  • 使用 preconnect,让用户提前连接到存放 Web Fonts 的域名
  • 对所需字符进行字体子集
  • 限制使用的重量变化的数量,不采用可变字体情况下,每个不同重量的字体需要一个字体文件
  • 探索可变字体
  • 尽可能使用系统字体
  • 使用F-mods来减少字体交换的影响

如果你确认,在使用 Web Fonts 时不能使用 font-display: optional 控制浏览器加载字体和渲染字体策略(必须使用 font-display: swap),那么就尽可能的使用 @font-face 规则加载一个系统字体作为该 Web Font 的备用字体,并且在该 @font-face 规则中配置 F-mods 描述符(ascent-overridedescent-overrideline-gap-override )和 size-adjust,尽可能让使用覆盖描述符修改后的系统字体更贴近 Web Fonts,更好的减少 Web Fonts 引起的布局偏移。