CSS Font Loading API

Baseline Widely available

This feature is well established and works across many devices and browser versions. It’s been available across browsers since January 2020.

CSS 字体加载 API 为你提供了动态加载字体资源的事件和接口。

备注: 此特性在 Web Worker 中可用。(你可通过 self.fonts 访问 FontFaceSet)。

概念和用法

在 CSS 中你可以使用 @font-face 规则下载字体,并使用 font-family 属性将字体应用于元素。但是,下载字体流程由客户端控制,大多数客户端仅会在首次需要该字体时才获取、加载该字体,这可能会导致明显的延迟。

CSS 字体加载 API 提供了控制和跟踪字体加载过程的能力,并允许你将其添加到 Document 或 Worker 的字体集中。将字体添加到 Document 或 Worker 的字体集中会让客户端在需要时自动获取、加载字体。字体可以在其被加入字体集之前或之后被加载,但是你必须先将字体添加到字体集,再将其用于绘图。

你可以通过为 FontFace 对象指定字体文件或 URL 字体源及其他属性来定义字体,其使用方式与 CSS @font-face 规则大致相同。FontFace 对象可以通过 Document.fontsWorkerGlobalScope.fonts 被添加到 DocumentWeb WorkerFontFaceSet 中。你可以使用 FontFaceFontFaceSet 对象下载字体,并监听加载完成事件。 FontFaceSet 还可用于确定加载页面所需的所有字体以及文档布局何时完成。

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

定义字体

你可以使用 FontFace 构造函数创建字体,该函数有 3 个参数:字体家族、字体源和可选的描述符。这些参数与 @font-face 的参数一致。

其中,字体源可以是字体文件的 ArrayBuffer,也可以是 URL 指向的字体文件。请注意,URL 字体源需要使用 url() 函数包裹 URL。

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

备注:@font-face 一样,一些描述符表示期望的字体属性并用于字体匹配,而其他描述符为设置、定义生成的字体的属性。例如,将 style 设置为“斜体”表示文件包含斜体字体,将由开发者指定一个符合此条件的文件。

对于二进制字体,如果字体定义有效并且成功加载会把 FontFace.status 设置为 loaded,否则会设置为 failed。对于 URL 字体,字体有效,且未被加载时 FontFace.status 会被设置为 unloaded,若字体无效则设置为 failed

向 Document 或 Worker 添加字体

你可以将字体添加到 Document 或 Worker 的 FontFaceSet 中,以允许客户端在需要时自动加载字体。你只能使用添加到 FontFaceSet 中的字体来渲染文本。

下面的代码显示如何添加字体到文档中。

js
// 定义字体
const font = new FontFace("myfont", "url(myfont.woff)", {
  style: "italic",
  weight: "400",
  stretch: "condensed",
});

// 把字体添加到 document.fonts(FontFaceSet)中
document.fonts.add(font);

加载字体

你可以通过调用 FontFace.load() 加载字体,或者通过调用 FontFaceSet.load() 加载已添加到 FontFaceSet 中的字体。注意,尝试加载已加载的字体不会生效。

以下代码演示如何定义字体并将其添加到 document 的字体中,然后加载字体。

js
// 定义字体
const font = new FontFace("myfont", "url(myfont.woff)");

// 把字体添加到 document.font(FontFaceSet)中
document.fonts.add(font);

// 加载字体
font.load();

// 等待到所有的字体都加载完毕
document.fonts.ready.then(() => {
  // 使用该字体渲染文字(如:在 canvas 中绘制)
});

注意,font.load() 返回一个 Promise,你可以通过调用 .then() 来处理字体加载的回调函数。在一些情况下,使用 document.fonts.ready 会更好,因为它会在文档布局完成且所有的字体都加载完成时触发。

接口

FontFace

表示单个可用的字体。

FontFaceSet

字体 API 的一个接口,支持检测它们(字体文件)的下载状态。

FontFaceSetLoadEvent

FontFaceSet 加载时触发的事件。

示例

简单字体加载

这是一个非常简单的示例,展示了从 Google Fonts 加载字体,并使用该字体在画布上绘制文本。并且该示例还会在字体创建和加载后,在文本框中打印字体状态的日志。

HTML

此代码定义用于绘制的画布和用于打印字体状态日志的文本区域。

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

JavaScript

首先,我们获取打印字体状态日志的文本框,以及用于使用字体绘制文本的画布。

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

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

接下来,我们定义一个 URL 源为 Google Fonts 的 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 返回的 Promise 对象。

结果

结果如下所示。它会使用下载了的字体在 Canvas 上绘制字体的名字,并显示字体加载状态的日志。

使用事件加载字体

此示例与上一个示例类似,不同之处在于它使用 FontFaceSet.load() 加载字体(而不是使用 font.load())。它还展示了如何监听字体完成加载事件。

HTML

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

JavaScript

下面的代码定义了用于绘制文本的 canvas 上下文和字体,并将其添加到 document 字体集中。

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 兑现,我们将使用该字体绘制文本。如果被拒绝,则会记录错误。

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 才会兑现。

当 promise 兑现时,我们遍历 document 字体集中的值。

js
document.fonts.ready.then(function () {
  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 兑现时的输出。

规范

Specification
CSS Font Loading Module Level 3
# fontface-interface

浏览器兼容性

BCD tables only load in the browser