CSS Font Loading API

Baseline 广泛可用 *

此特性已相当成熟,可在许多设备和浏览器版本上使用。自 ⁨2020 年 1 月⁩ 起,所有主流浏览器均已支持。

* 此特性的某些部分可能存在不同级别的支持。

注意:此功能在 Web Workers 中可用。

CSS 字体加载 API 提供用于动态加载字体资源的事件和接口。

概念与用法

CSS 样式表允许作者使用自定义字体;通过 @font-face 规则指定要下载的字体,并通过 font-family 属性将其应用于元素。用户代理控制字体下载的时间点。大多数代理仅在首次需要字体时才获取和加载字体,这可能导致明显的延迟。

CSS 字体加载 API 通过让作者控制和跟踪字体面(font face)何时被获取和加载,以及何时被添加到文档或工作程序拥有的字体面集合中,来解决此问题。将字体面添加到文档或工作程序的字体面集合中,允许用户代理在需要时自动获取和加载关联的字体资源。字体面可以在添加到着色集之前或之后加载,但必须先添加到着色集中才能用于绘制。

字体面在 FontFace 对象中定义,该对象指定一个二进制或 URL 字体源以及字体的其他属性,这与 CSS @font-face 规则非常相似。FontFace 对象分别使用 Document.fontsWorkerGlobalScope.fonts 添加到文档或工作程序的 FontFaceSet 中。作者可以使用 FontFaceFontFaceSet 触发字体下载,并监视加载完成情况。FontFaceSet 还可以用来确定页面所需的所有字体何时加载完成以及文档布局何时完成。

FontFace.status 属性指示字体面的加载状态:unloadedloadingloadedfailed。此状态最初为 unloaded。当文件正在下载或字体数据正在处理时,它被设置为 loading,如果字体定义无效或字体数据无法加载,则设置为 failed。当字体面的数据已成功获取(如果需要)并加载后,状态被设置为 loaded

定义字体面

字体面使用 FontFace 构造函数 创建,该构造函数接受以下参数:字体系列、字体源以及可选的描述符。这些参数的格式和语法与等效的 @font-face 定义相同。

字体源可以是 ArrayBuffer 中的二进制数据,也可以是 URL 处的字体资源。使用 URL 源的典型字体面定义如下所示。请注意,URL 字体源需要 url() 函数。

js
const font = new FontFace("my-font", 'url("my-font.woff")', {
  style: "italic",
  weight: "400",
  stretch: "condensed",
});

注意:@font-face 一样,一些描述符代表字体数据中的预期数据,并用于字体匹配,而另一些描述符则实际设置/定义生成的字体面的属性。例如,将 style 设置为“italic”表示文件包含斜体字体;作者有责任指定一个文件,其中该值为真。

具有二进制源的字体面将在字体定义有效且字体数据可加载时自动加载 — 成功时 FontFace.status 设置为 loaded,否则设置为 failed。具有 URL 源的字体面会被验证但不会自动加载 — 如果字体面定义有效,FontFace.status 被设置为 unloaded,否则设置为 failed

将字体添加到文档或工作程序

字体面通常会添加到文档或工作程序的 FontFaceSet 中,以便用户代理在需要时自动加载字体,并且必须添加字体才能用于渲染文本。

下面的代码显示了一个字体面被添加到文档中。

js
// Define a FontFace
const font = new FontFace("my-font", 'url("my-font.woff")', {
  style: "italic",
  weight: "400",
  stretch: "condensed",
});

// Add to the document.fonts (FontFaceSet)
document.fonts.add(font);

加载字体

可以通过调用 FontFace.load() 来手动加载字体面,或者如果字体面已添加到 FontFaceSet 中,则可以通过调用 FontFaceSet.load() 来加载。请注意,尝试加载已加载的字体无效。

下面的代码演示了如何定义一个字体面,将其添加到文档字体中,然后启动字体加载。

js
// Define a FontFace
const font = new FontFace("my-font", 'url("my-font.woff")');

// Add to the document.fonts (FontFaceSet)
document.fonts.add(font);

// Load the font
font.load();

// Wait until the fonts are all loaded
document.fonts.ready.then(() => {
  // Use the font to render text (for example, in a canvas)
});

请注意,font.load() 返回一个 Promise,因此我们可以通过在后面链式调用 then 来处理字体加载的完成。在某些情况下,使用 document.fonts.ready 可能更好,因为它仅在文档中的所有字体都已解析且布局完成后调用。

接口

FontFace

表示一个可用的单个字体面。

FontFaceSet

一个加载字体面并检查其下载状态的接口。

FontFaceSetLoadEvent

每当 FontFaceSet 加载时触发。

示例

基本字体加载

这是一个非常简单的示例,展示了如何从 Google Fonts 加载字体并将其用于在 Canvas 上绘制文本。该示例还在创建后和加载后立即记录 status

HTML

此代码定义了一个用于绘制的 Canvas 和一个用于记录的 textarea。

html
<canvas id="js-canvas"></canvas>
<textarea id="log" rows="3" cols="100"></textarea>

JavaScript

首先,我们获取将用于记录的元素以及将用于在下载的字体中渲染文本的 Canvas。

js
const log = document.getElementById("log");

const canvas = document.getElementById("js-canvas");
canvas.width = 650;
canvas.height = 75;

接下来,我们定义一个具有 Google Fonts URL 源的 FontFace,并将其添加到 document.fonts。然后我们记录字体状态,这应该是 unloaded

js
const bitterFontFace = new FontFace(
  "FontFamily Bitter",
  'url("https://fonts.gstatic.com/s/bitter/v7/HEpP8tJXlWaYHimsnXgfCOvvDin1pK8aKteLpeZ5c0A.woff2")',
);
document.fonts.add(bitterFontFace);
log.textContent += `Bitter font: ${bitterFontFace.status}\n`; // > Bitter font: unloaded

然后,我们调用 FontFace.load() 方法加载字体面,并等待返回的 Promise。Promise 解析后,我们记录加载的状态(应为 loaded)并在 Canvas 上用加载的字体绘制文本。

js
bitterFontFace.load().then(
  () => {
    log.textContent += `Bitter font: ${bitterFontFace.status}\n`; // > Bitter font: loaded

    const ctx = canvas.getContext("2d");
    ctx.font = '36px "FontFamily Bitter"';
    ctx.fillText("Bitter font loaded", 20, 50);
  },
  (err) => {
    console.error(err);
  },
);

请注意,我们也可以等待 FontFace.loaded 属性返回的 Promise,或者等待 FontFaceSet.ready

结果

结果如下所示。它应该显示在 Canvas 上用下载的字体绘制的字体名称,以及显示加载前后加载状态的日志。

使用事件加载字体

此示例与上一个示例类似,但它使用 FontFaceSet.load() 加载字体。它还演示了如何监听字体加载事件。

HTML

html
<canvas id="js-canvas"></canvas>
<textarea id="log" rows="25" cols="100"></textarea>

JavaScript

下面的代码定义了一个用于绘制文本的 Canvas 上下文,定义了一个字体面,并将其添加到文档字体面集合中。

js
const log = document.getElementById("log");

const canvas = document.getElementById("js-canvas");
canvas.width = 650;
canvas.height = 75;
const ctx = canvas.getContext("2d");

const oxygenFontFace = new FontFace(
  "FontFamily Oxygen",
  'url("https://fonts.gstatic.com/s/oxygen/v5/qBSyz106i5ud7wkBU-FrPevvDin1pK8aKteLpeZ5c0A.woff2")',
);
document.fonts.add(oxygenFontFace);
log.textContent += `Oxygen status: ${oxygenFontFace.status}\n`;

接下来,我们在字体面集合上使用 load() 来加载字体,并指定要加载的字体。该方法返回一个 Promise。如果 Promise 成功解析,我们就使用该字体绘制一些文本。如果 Promise 被拒绝,则记录错误。

js
document.fonts.load("36px FontFamily Oxygen").then(
  (fonts) => {
    log.textContent += `Bitter font: ${fonts}\n`; // > Oxygen font: loaded
    log.textContent += `Bitter font: ${oxygenFontFace.status}\n`; // > Oxygen font: loaded
    ctx.font = '36px "FontFamily Oxygen"';
    ctx.fillText("Oxygen font loaded", 20, 50);
  },
  (err) => {
    console.error(err);
  },
);

与其等待 Promise,我们也可以使用事件来跟踪字体加载操作。下面的代码监听 loadingloadingerror 事件,并记录每种情况下的字体面数量。在 loadingdone 事件监听器中,我们还会遍历字体面并记录字体系列的名称。

js
document.fonts.addEventListener("loading", (event) => {
  log.textContent += `loading_event: ${event.fontfaces.length}\n`;
});
document.fonts.addEventListener("loadingerror", (event) => {
  log.textContent += `loadingerror_event: ${event.fontfaces.length}\n`;
});
document.fonts.addEventListener("loadingdone", (event) => {
  log.textContent += `loadingdone_event: ${event.fontfaces.length}\n`;
  event.fontfaces.forEach((value) => {
    log.textContent += `  fontface: ${value.family}\n`;
  });
});

最后一段代码演示了如何使用 FontFaceSet.ready 返回的 Promise 来监视字体加载的完成情况。与其他机制不同,这在文档中定义的所有字体都已下载并且布局已完成时才返回。

当 Promise 解析时,我们遍历文档字体面中的值。

js
document.fonts.ready.then(() => {
  log.textContent += `\nFontFaces in document: ${document.fonts.size}.\n`;

  for (const fontFace of document.fonts.values()) {
    log.textContent += "FontFace:\n";
    for (const property in fontFace) {
      log.textContent += `  ${property}: ${fontFace[property]}\n`;
    }
  }
});

结果

下面的输出显示了用“Oxygen”字体绘制的文本。它还显示了来自事件和 document.fonts.ready Promise 解析时的日志。

规范

规范
CSS 字体加载模块等级 3
# fontface-interface

浏览器兼容性