CSS 字体加载 API

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

注意:此功能在 Web Workers 中可用(self.fonts 提供对 FontFaceSet 的访问权限)。

概念和用法

CSS 样式表允许作者使用自定义字体;使用 @font-face 规则指定要下载的字体,并使用 font-family 属性将它们应用于元素。字体下载的时机由用户代理控制。大多数代理只在第一次需要字体时才获取和加载字体,这会导致明显的延迟。

CSS 字体加载 API 通过让作者控制和跟踪字体面何时被获取和加载,以及何时被添加到文档或工作线程拥有的字体面集中,解决了这个问题。将字体面添加到文档或工作线程字体面集中,允许用户代理在需要时自动获取和加载相关的字体资源。字体面可以在添加到字体面集之前或之后加载,但它必须添加到该集中才能用于绘制。

字体面在 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("myfont", "url(myfont.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("myfont", "url(myfont.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("myfont", "url(myfont.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 加载字体并将其用于将文本绘制到画布上的过程。该示例还记录了创建和加载后的 status

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;

接下来,我们定义一个 FontFace,它具有来自 Google Fonts 的 URL 源,并将其添加到 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)并将文本以已加载的字体绘制到画布上。

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。

结果

结果如下所示。它应该显示在画布上以下载的字体绘制的字体名称,以及在加载前后显示的加载状态记录。

使用事件加载字体

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

HTML

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

JavaScript

以下代码定义了用于绘制文本的画布上下文,定义了字体面,并将字体面添加到文档字体面集中。

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(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 解析时的记录。

规范

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

浏览器兼容性

BCD 表格只能在浏览器中加载