Web 字体与浏览器字体选择

最后更新: 2024-09-03 19:44:16 +0800 +0800
tags: Css | Fonts | Performance | Web
原文以英文撰写。您正在阅读AI翻译版本。

字样或字体家族用于归组相关字体面。字体面具有字重、样式和字宽等属性。字形是为字符或字符序列选择并渲染出的形状。

浏览器按字符进行字体选择。因此,一个段落可以为拉丁字母使用一个字体家族,为 CJK 字符使用另一个字体家族,并为表情符号使用表情符号字体。

构建字体家族栈 #

font-family 是由字体家族名称和通用字体家族组成的有序列表:

css
body {
  font-family:
    "Example Sans",
    "Noto Sans",
    system-ui,
    sans-serif;
}

包含空格的字体家族名称应使用引号。由于不同操作系统提供的本地字体不同,应以通用字体家族作为字体栈的末尾项。

通用字体家族包括 serif、sans-serif、monospace、cursive、fantasy、system-ui、ui-serif、ui-sans-serif、ui-monospace、ui-rounded、emoji、math 和 fangsong。较新的通用名称在不同环境中的支持情况不同,因此应保留一个范围较广的最终回退项。

对于每个字符,浏览器会考虑可用的字体面、请求的样式、字重和字宽;如果选定的字体面缺少相应字形,则回退到其他字体。回退可能导致同一行内的度量发生变化,因此应测试生产内容所包含的文字系统。

声明可下载的字体面 #

@font-face 将字体家族和字体面范围关联到一个资源:

css
@font-face {
  font-family: "Example Sans";
  src: url("/fonts/example-sans-latin.woff2") format("woff2");
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
}

该声明不会应用字体。后续的 font-family 声明才会选择它。

除非文件包含相应的可变轴,否则应分别声明斜体和正体字体面:

css
@font-face {
  font-family: "Example Sans";
  src: url("/fonts/example-sans-italic.woff2") format("woff2");
  font-style: italic;
  font-weight: 400 700;
  font-display: swap;
}

准确的描述符可以在真实字体面存在时,防止浏览器合成粗体或斜体。

只有在合成粗体、斜体或小型大写字母会违背设计要求,并且所有必需的字体面都可用时,才应设置 font-synthesis: none。否则,当选定字体缺少请求的字体面时,合成可以保留强调效果。

理解 Web 字体格式 #

现代网站应优先使用 WOFF2。它使用针对 Web 传输设计的压缩来封装 OpenType 或 TrueType 字体数据。

FormatRole
TrueType (.ttf)使用 TrueType 轮廓的桌面和平台字体格式
OpenType (.otf)支持多种轮廓和排版技术的字体容器
WOFF带有压缩和元数据的 Web 容器
WOFF2使用更强压缩的新一代 Web 容器
EOT历史上的 Internet Explorer Web 格式
SVG fonts已废弃的文本字体机制;SVG 仍适用于图标和图形

格式转换不会授予再分发权。在自行托管或子集化字体之前,应核实字体许可证。

有意使用可变字体 #

可变字体在一个文件中存储一系列设计。已注册的轴包括字重(wght)、字宽(wdth)、光学尺寸(opsz)、倾斜度(slnt)和斜体(ital)。

css
@font-face {
  font-family: "Example Variable";
  src: url("/fonts/example-variable.woff2") format("woff2");
  font-weight: 300 800;
  font-stretch: 75% 125%;
  font-style: normal;
}

对于已注册的轴,应使用 font-weight 和 font-stretch 等高级属性。font-variation-settings 适用于自定义轴或精确的底层控制。

可变字体面参与与静态字体面相同的字体家族匹配算法。请求值位于声明的轴范围内时,会选择插值实例。当请求值位于范围之外时,匹配会使用最接近的受支持值,或字体家族中另一个匹配程度更高的字体面;如果选定字体面缺少某个字符,则会针对该字符继续遍历回退字体家族列表。应声明准确的范围,从而避免浏览器选择无法生成所请求实例的字体面。

可变字体文件并不会自动小于所有静态字体集合。当页面需要使用足够大的已表示范围,以抵消额外的可变数据时,可变字体才更高效。

控制加载和布局偏移 #

font-display 定义回退行为:

  • block 允许在回退前有一段很短的不可见文本时间。
  • swap 立即显示回退字体,并在字体面到达后进行替换。
  • fallback 使用较短的阻塞期和有限的替换期。
  • optional 允许浏览器在受限条件下继续使用回退字体。

swap 可以保持文本可读,但度量不匹配可能导致布局偏移。应选择比例相近的回退字体,或使用度量覆盖:

css
@font-face {
  font-family: "Example Sans Fallback";
  src: local("Arial");
  size-adjust: 102%;
  ascent-override: 92%;
  descent-override: 24%;
  line-gap-override: 0%;
}

覆盖值应根据测量得到的字体度量推导,而不是凭猜测设置。

只预加载初始渲染期间所需的字体面:

html
<link
  rel="preload"
  href="/fonts/example-sans-latin.woff2"
  as="font"
  type="font/woff2"
  crossorigin>

预加载 URL、CORS 模式和 @font-face URL 必须匹配,否则浏览器可能下载两次资源。过度预加载会与关键 CSS、脚本和图像争夺资源。

按字符覆盖范围进行子集化 #

unicode-range 允许浏览器仅在文本需要相应代码点时下载字体面:

css
@font-face {
  font-family: "Example Sans";
  src: url("/fonts/example-sans-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153;
}

所有子集合在一起必须覆盖组合标记、标点、符号以及所有受支持的语言。缺少组合标记可能产生无法与基础字符对齐的回退字形。

访问本地字体 #

在支持的浏览器中,window.queryLocalFonts() 可在获得用户许可后枚举本地安装的字体:

js
const fonts = await window.queryLocalFonts();
const matching = fonts.filter((font) => font.family === "Noto Sans");

字体枚举会暴露可识别信息,因此受到严格的权限和可用性限制。不要让普通页面渲染依赖它;CSS 字体匹配已经会使用字体家族栈中指定的已安装字体。

@font-face 中的 local() 来源也会将已安装字体的可用性暴露给 CSS 匹配。出于隐私保护,浏览器可能忽略本地来源,或降低其精确度,因此应在本地提示之后提供可下载的来源:

css
@font-face {
  font-family: "Example Sans";
  src:
    local("Example Sans"),
    url("/fonts/example-sans.woff2") format("woff2");
}

不要将长列表的本地字体探测用作指纹识别机制。

评论

在 GitHub 上阅读或评论(发表评论需要登录)