# 字体与图片优化：font-display、picture 与 AVIF/WebP

> font-display CSS 描述符如何在网络字体加载期间控制文本可见性，img 的 loading 与 fetchpriority 属性以及 picture 元素如何控制图片的加载时机与方式，以及根据 MDN，AVIF 与 WebP 相比 JPEG 和 PNG 能压缩得更小。

**一句话：** `font-display` CSS 描述符控制在网络字体加载期间文本是保持不可见还是以
回退字体渲染，而 `<img>` 元素的 `loading` 属性控制何时获取图片、`fetchpriority`
提示其相对优先级，AVIF、WebP 等格式则改变这次获取需要传输多少数据。

## 用 font-display 控制字体加载期间的可见性

根据 MDN，`font-display` 为 `@font-face` 规则定义了一个阻塞期（浏览器为等待字体而
隐藏文本的时长）和一个交换期（字体到达后仍会被换入的时长）。`swap` 值给出极短的
阻塞期，因此文本几乎立即以回退字体渲染，网络字体到达后再换入：

```css
@font-face {
  font-family: "Brand Sans";
  src: url("/fonts/brand-sans.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}
```

根据 MDN，`font-display` 属于 Baseline 广泛可用，自 2020 年 1 月起获得支持。

## 延迟加载与设置图片优先级

根据 MDN，`<img>` 元素的 `loading` 属性中，`lazy` 会将加载推迟到图片被计算为接近
视口时，而默认值 `eager` 会立即加载；这种延迟仅在启用 JavaScript 时才生效——当脚本
被禁用时，`loading="lazy"` 不会产生任何效果。`fetchpriority` 属性则单独提示该次获取的
相对优先级：

```html
<img
  src="/images/hero.avif"
  width="1200"
  height="630"
  loading="eager"
  fetchpriority="high"
  alt="Product hero shot"
/>
<img
  src="/images/gallery-3.avif"
  width="400"
  height="300"
  loading="lazy"
  alt="Gallery photo 3"
/>
```

根据 MDN，每张图片都建议显式设置 `width` 与 `height`，对 `loading="lazy"` 的图片尤其
如此：没有这两个属性的图片在加载完成前布局尺寸为 `0`×`0`，而根据 MDN，一张浏览器认为
没有尺寸的未加载图片，如果它从未与某个可见元素的区域相交，就可能永远不会加载。

## 支持情况

根据 MDN，`font-display` 属于 Baseline 广泛可用。对于图片，`<img>` 的 `loading` 与
`fetchpriority` 在当前浏览器中已被广泛支持。AVIF 在 Chrome、Edge 与 Opera 中受支持，
Firefox 从 Firefox 93 起、Safari 从 Safari 16.1 起为其添加了支持——支持历史比 WebP 更短；
根据 MDN，WebP 在 Chrome、Edge、Firefox、Opera 与 Safari 的当前版本中均受支持，因此格式
回退链对 AVIF 而言仍然重要，尤其是面向使用较旧浏览器版本的用户时。

## 用 AVIF 和 WebP 搭配 JPEG 回退提供图片

根据 MDN，AVIF 在质量相近的情况下通常比 JPEG 小约 50%，WebP 的有损模式通常比 JPEG
小 25%-35%。`<picture>` 包裹一个或多个 `<source>` 元素，并搭配一个回退用的 `<img>`：

```html
<picture>
  <source srcset="/images/hero.avif" type="image/avif" />
  <source srcset="/images/hero.webp" type="image/webp" />
  <img src="/images/hero.jpg" width="1200" height="630" alt="Product hero shot" />
</picture>
```

## 特性检测与回退

根据 MDN，`HTMLImageElement.decode()` 返回一个 Promise，图片准备好可用时会 resolve，
解码失败时则会以 `EncodingError` reject。出现这种情况可能是因为图片请求失败、调用
`decode()` 后请求发生变化，或图片数据已损坏。代码可以在这次尝试被 reject 时选择回退，
但仅凭这次 reject 不能确定浏览器缺少 AVIF 解码支持：

```js
let avifSupportPromise;
function canDecodeAvifProbe() {
  if (avifSupportPromise) return avifSupportPromise;
  if (!('Image' in window)) {
    avifSupportPromise = Promise.resolve(false); // 没有 Image 构造函数：跳过探测。
    return avifSupportPromise;
  }
  const probe = new Image();
  probe.src =
    'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAEAAAABAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQAMAAAAABNjb2xybmNseAACAAIABoAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAAB9tZGF0EgAKCBgABogQEDQgMgkxIABJSElBQkJB';
  avifSupportPromise = probe
    .decode()
    .then(() => true)
    .catch(() => false); // 本次尝试失败：选择回退图片。
  return avifSupportPromise;
}
```

## 实用清单

- [ ] 根据 MDN，通过 `<picture>` 或其他方式为 AVIF 提供 WebP、JPEG 或 PNG 回退——
      AVIF 需要完整下载后才能渲染任何内容，且它在当前浏览器中的支持历史比 WebP 更短。
- [ ] 为每张图片设置 `width` 与 `height`，尤其是 `loading="lazy"` 的图片，这样浏览器
      才有已知的布局尺寸，图片才能被找到并加载。
- [ ] 根据 MDN，`fetchpriority` 只是向浏览器提示相对优先级，而非保证——因此把 `high`
      留给最重要的那些图片。
- [ ] 根据 MDN，`font-display` 的默认值（`auto`）由浏览器自行决定阻塞行为；如果想要
      `swap` 或 `optional` 所定义的那种极短阻塞期，需要显式设置。
- [ ] 参见[资源提示](/zh/reference/performance/resource-hints/)了解 `rel="preload"`。

## 相关参考

- [Resource hints: rel="preload" and rel="preconnect"](/zh/reference/performance/resource-hints/)
- [Core Web Vitals](/zh/reference/performance/core-web-vitals/)