字体是实现良好的设计、品牌推广、可读性和无障碍功能的基础。网页字体可实现所有上述目标以及其他目标:文本可选取、可搜索、可缩放并支持高 DPI,无论屏幕尺寸和分辨率如何,均可提供一致并且锐利的文本渲染。网页字体是实现良好的设计、用户体验和性能的关键所在。
网页字体优化是总体性能策略的一个关键部分。每个字体都是一项附加资源,并且某些字体可能会阻止文本的渲染,但不能仅仅因为网页使用了 WebFonts,就认为它只能降低渲染速度。相反,如果对字体进行优化,再通过制定明智的策略对字体在网页上的加载和应用方式作出规定,就可以帮助减小网页总大小,并缩短网页渲染时间。
网页字体剖析
网页字体是一个字形集合,而每个字形是描述字母或符号的矢量形状。因此,特定字体文件的大小由两个简单变量决定:每个字形矢量路径的复杂程度和特定字体中字形的数量。例如,Open Sans 是其中一种最流行的 Web 字体,包含 897 个字形,其中包括拉丁文、希腊文和西里尔文字符。
选择字体时,请务必考虑它支持的字符集。如果您需要将页面内容本地化成多种语言,就应该使用一种能够为用户带来一致的外观和体验的字体。例如,Google 的 Noto 字体系列旨在支持全世界的语言。但请注意,Noto(含所有语言)ZIP 格式下载文件的总大小超过了 1.1GB。
在本文中,您将了解如何减小 Web 字体的传送文件大小。
网页字体格式
目前网络上使用的推荐字体容器格式有两种:
WOFF 和 WOFF 2.0 得到广泛支持,所有新型浏览器都支持它们。
- 将 WOFF 2.0 变体提供给新型浏览器。
- 如果绝对必要(例如,您仍需要支持 Internet Explorer 11),请将 WOFF 作为后备服务。
- 或者,您也可以考虑不为旧版浏览器使用 Web 字体,而是回退到系统字体。对于配置较低的旧款设备,这种方法可能也能带来更出色的性能。
- 由于 WOFF 和 WOFF 2.0 涵盖了仍在使用的现代浏览器和旧版浏览器的所有基础,因此不再需要使用 EOT 和 TTF,这可能会导致 Web 字体下载时间延长。
网页字体和压缩
WOFF 和 WOFF 2.0 都具有内置压缩功能。WOFF 2.0 的内部分压缩使用 Brotli,与 WOFF 相比,压缩率最高可提高 30%。如需了解详情,请参阅 WOFF 2.0 评估报告。
最后,值得注意的是,某些字体格式包含附加的元数据,如字体提示和字距调整信息,这些信息在某些平台上可能并非必要信息,这样便可进一步优化文件大小。例如,Google Fonts 为每一种字体维护有 30 多种优化过的变体,并自动检测和提供适合每一个平台和浏览器的最佳变体。
使用 @font-face
定义字体系列
您可以通过 @font-face
CSS at-rule 定义特定字体资源的位置、其样式特性及其应该用于的 Unicode 代码点。可组合使用上述 @font-face
声明来构建“字体系列”,浏览器将使用该系列来评估哪些字体资源需要下载并应用到当前网页。
考虑使用可变字体
如果您需要某种字体的多个变体,可变字体可以显著缩减字体的文件大小。您可以加载一个包含所有信息的文件,而无需加载正文和粗体样式以及它们的斜体版本。不过,可变字体文件大小将大于单个字体变体,但小于许多变体的组合。与其提供一个大型可变字体,不如先提供关键字体变体,然后再下载其他变体。
现在,所有新型浏览器都支持可变字体,如需了解详情,请参阅网页可变字体简介。
选择合适的格式
每个 @font-face
声明都提供字体系列的名称,它充当多个声明的逻辑组、字体属性(如样式、粗细和拉伸)以及为字体资源指定位置优先级列表的 src 描述符。
@font-face {
font-family: 'Awesome Font';
font-style: normal;
font-weight: 400;
src: local('Awesome Font'),
url('/fonts/awesome.woff2') format('woff2'),
/* Only serve WOFF if necessary. Otherwise,
WOFF 2.0 is fine by itself. */
url('/fonts/awesome.woff') format('woff');
}
@font-face {
font-family: 'Awesome Font';
font-style: italic;
font-weight: 400;
src: local('Awesome Font Italic'),
url('/fonts/awesome-i.woff2') format('woff2'),
url('/fonts/awesome-i.woff') format('woff');
}
首先,请注意以上示例使用两种样式(normal 和 italic)来定义单个 Awesome Font 系列,其中的每个样式均指向一个不同的字体资源集。每个 src
描述符则又包含一个用逗号分隔的资源变体优先级列表:
local()
指令用于引用、加载和使用安装在本地的字体。如果用户的系统上已安装该字体,则此方法会完全绕过网络,速度最快。url()
指令用于加载外部字体,它可以包含可选的format()
提示,指示由提供的网址引用的字体格式。
当浏览器确定需要字体时,它会按指定顺序循环访问提供的资源列表,并尝试加载相应的资源。例如,接着上面的示例:
- 浏览器执行页面布局并确定需要使用哪些字体变体来渲染网页上的指定文本。浏览器不会下载不属于网页 CSS 对象模型 (CSSOM) 的字体,因为它们不是必需的。
- 对于每一种必需字体,浏览器会检查字体文件是否位于本地。
- 如果字体文件不在本地,则浏览器会遍历外部定义:
- 如果存在格式提示,则浏览器会在启动下载之前检查其是否支持。如果浏览器不支持此提示,则会前进到下一格式提示。
- 如果不存在格式提示,则浏览器会下载资源。
您可以将本地和外部指令与相应的格式提示相结合来指定所有可用字体格式,其余工作交由浏览器进行处理。浏览器确定需要哪些资源,并选择最佳格式。
Unicode 范围子集
除了样式、粗细和拉伸等字体属性外,您还可以通过 @font-face
规则定义各资源支持的 Unicode 代码点集。这样一来,您便可将大型 Unicode 字体拆分成较小的子集(例如拉丁文、西里尔文和希腊文子集),并且只下载在特定网页上渲染文本所需的字形。
您可以通过 unicode-range
描述符指定一个用逗号分隔的范围值列表,其中的每个值都可能采用下列三种不同形式中的一种:
- 单一代码点(例如
U+416
) - 间隔范围(例如
U+400-4ff
):表示范围的开始代码点和结束代码点 - 通配符范围(例如
U+4??
):?
字符表示任何十六进制数字
例如,您可以将 Awesome Font 系列拆分成拉丁文和日文子集,其中的每个子集将由浏览器根据需要下载:
@font-face {
font-family: 'Awesome Font';
font-style: normal;
font-weight: 400;
src: local('Awesome Font'),
url('/fonts/awesome-l.woff2') format('woff2');
/* Latin glyphs */
unicode-range: U+000-5FF;
}
@font-face {
font-family: 'Awesome Font';
font-style: normal;
font-weight: 400;
src: local('Awesome Font'),
url('/fonts/awesome-jp.woff2') format('woff2');
/* Japanese glyphs */
unicode-range: U+3000-9FFF, U+ff??;
}
您可以通过使用 Unicode range 子集,以及为字体的每个样式变体使用单独的文件,定义一个下载起来更快速并且更高效的复合字体系列。访问者将只下载其需要的变体和子集,并且不会强制他们下载可能永远不会在网页上看到或使用的子集。
几乎所有浏览器都支持 unicode-range
。为了与旧版浏览器兼容,您可能需要回退到“手动子集内嵌”。在这种情况下,您必须回退以提供包含所有必要子集的单一字体资源,并向浏览器隐藏其余子集。例如,如果网页只使用拉丁文字符,那么您可以去除其他字形,并将该特定子集作为一个独立资源提供。
- 确定需要哪些子集:
- 如果浏览器支持 unicode-range 子集内嵌,则它会自动选择正确的子集。网页只需提供子集文件并在
@font-face
规则中指定相应的 unicode-range。 - 如果浏览器不支持 unicode-range 子集内嵌,则网页需要隐藏所有多余的子集,即开发者必须指定需要的子集。
- 如果浏览器支持 unicode-range 子集内嵌,则它会自动选择正确的子集。网页只需提供子集文件并在
- 生成字体子集:
- 使用开源的 pyftsubset 工具对您的字体进行子集内嵌和优化。
- 默认情况下,某些字体服务器(例如 Google Fonts)会自动进行子集选择。
- 某些字体服务允许通过自定义查询参数手动内嵌子集,您可以利用这些参数手动指定网页需要的子集。请查阅您的字体提供商的文档。
字体选择和合成
每个字体系列都可能由多个样式变体(常规、加粗、斜体)和适用于每个样式的多个粗细组成。其中的每个粗细又可能包含迥异的字形形状 - 例如不同的间距、大小调整或完全不同的形状。
上图以图解方式说明了一个提供三种不同加粗粗细的字体系列:
- 400(常规)。
- 700(粗体)。
- 900(特粗)。
浏览器会将所有其他中间变体(以灰色表示)自动映射到最接近的变体。
如果指定的某个粗细不存在对应的字体,则使用相近粗细的字体。一般而言,加粗粗细映射到粗细较粗的字体,而较细粗细则映射到粗细较细的字体。
CSS 字体匹配算法
斜体变体也适用类似的逻辑。字体设计者控制其将产生哪些变体,而您控制将在网页上使用哪些变体。由于每个变体都会单独下载,因此最好将变体数量保持在较低水平。例如,您可以为 Awesome Font 系列定义两种加粗变体:
@font-face {
font-family: 'Awesome Font';
font-style: normal;
font-weight: 400;
src: local('Awesome Font'),
url('/fonts/awesome-l.woff2') format('woff2');
/* Latin glyphs */
unicode-range: U+000-5FF;
}
@font-face {
font-family: 'Awesome Font';
font-style: normal;
font-weight: 700;
src: local('Awesome Font'),
url('/fonts/awesome-l-700.woff2') format('woff2');
/* Latin glyphs */
unicode-range: U+000-5FF;
}
上例声明的 Awesome Font 系列由两项资源组成,它们涵盖同一拉丁文字形集 (U+000-5FF
),但提供两种不同的“粗细”:常规 (400) 和加粗 (700)。不过,如果您的其中一个 CSS 规则指定了一种不同的字体粗细,或者将 font-style
属性设置为 italic
,那会怎么样?
- 如果未找到精确字体匹配项,浏览器将以最接近的匹配项替代。
- 如果未找到样式匹配项(例如,在上例中未声明任何倾斜变体),则浏览器将合成其自己的字体变体。
上例以图解方式说明了 Open Sans 的实际字体与合成字体结果之间的差异。所有合成变体都是依据单个 400 粗细的字体生成的。您可以看出,结果存在显著差异。其中并未详细说明如何生成加粗和倾斜变体。因此,结果将因浏览器的不同而发生变化,并且与字体的相关度极高。
网页字体大小优化核对清单
- 审核并监控您的字体使用情况:不要在网页上使用过多字体,并且对于每一种字体,最大限度减少使用的变体数量。这将有助于为您的用户带来更加一致且更加快速的体验。
- 尽可能避免使用旧版格式:EOT、TTF 和 WOFF 格式比 WOFF 2.0 大。EOT 和 TTF 是完全不必要的格式,如果您需要支持 Internet Explorer 11,则可以接受 WOFF。如果您仅定位到新型浏览器,则仅使用 WOFF 2.0 是最简单、性能最高的选项。
- 对您的字体资源进行子集内嵌:许多字体都可进行子集内嵌,或者拆分成多个 unicode-range 以仅提供特定网页需要的字形。这样即可减小文件大小,并提高资源的下载速度。不过,在定义子集时要注意针对字体重复使用进行优化。例如,您一定不希望在每个网页上都下载不同但重叠的字符集。最好根据文字系统(例如拉丁文和西里尔文)进行子集内嵌。
- 在
src
列表中优先列出local()
:在src
列表中首先列出local('Font Name')
可确保不会针对已安装的字体发出 HTTP 请求。 - 使用 Lighthouse 测试文本压缩。
对 Largest Contentful Paint (LCP) 和 Cumulative Layout Shift (CLS) 的影响
根据网页的内容,文本节点可能会被视为 Largest Contentful Paint (LCP) 的候选项。因此,请务必按照本文中的建议,确保您的 Web 字体尽可能小,以便用户尽快看到您网页上的文字。
如果您担心,尽管您进行了优化,但由于 Web 字体资源较大,网页文字可能需要很长时间才能显示,那么 font-display
属性提供了一些设置,可帮助您在字体下载期间避免文字不可见。不过,使用 swap
值可能会导致严重的布局偏移,从而影响您网站的 Cumulative Layout Shift (CLS)。请尽可能考虑使用 optional
或 fallback
值。
如果您的 Web 字体对品牌推广至关重要,进而对用户体验也至关重要,不妨考虑预加载字体,以便浏览器提前请求这些字体。如果您使用 font-display: swap
,这可以缩短交换期;如果您不使用 font-display
,则可以缩短阻塞期。