性能 · 概念
字体与图片优化
发布于 更新于
在多数页面上,字体和图片就是决定 Largest Contentful Paint 与 Cumulative Layout Shift 的那些字节:阻塞文本或过晚替换的 Web 字体会移动每一行,首屏被懒加载或本可用 AVIF 却以 JPEG 提供的图片会推迟最大绘制。控制手段都是声明式的:@font-face 里的 font-display 与 size-adjust,图片上的 loading、fetchpriority、decoding、width、height 与 <picture>。
字体和图片由不同机制处理,所以分开说明。
font-display 为一条 @font-face 设定两个计时器:阻塞期,其间文本以不可见方式绘制、等待字体加载;替换期,其间迟到的字体仍可替换后备字体。swap 让阻塞期接近零,文本立刻以后备字体出现、字体到达后重排;optional 让阻塞期约为 100 ms、替换期为零,错过期限的字体只在下一次页面访问使用,绝不引起重排;fallback 介于两者之间;block 最长隐藏文本 3 s(font-display,developer.mozilla.org)。该描述符存在于 Chrome 60、Firefox 58 与 Safari 11.1(BCD css.at-rules.font-face.font-display)。
swap 引起的重排用度量匹配的后备字体消除:第二条 @font-face 指向本机系统字体,并用 size-adjust、ascent-override、descent-override(Chrome 92、Firefox 92、Safari 17)让它的行高与 Web 字体相同。度量一致时替换只改变字形而不改变换行,布局偏移得分保持为零。
loading="lazy" 把图片的抓取推迟到它接近视口(Chrome 77、Firefox 75、Safari 15.4),绝不能用在 LCP 图片上,否则浏览器会很晚才发现它。fetchpriority="high" 在预加载扫描器中把图片的抓取优先级从默认的 Low 提到 High,当 LCP 图片要与脚本竞争时正需要它(Chrome 101、Firefox 132、Safari 17.2)。decoding="async" 让解码在主线程之外进行。width 与 height 在图片加载前就给它布局盒;没有它们,懒加载图片没有尺寸,浏览器可能判定它永远不会接近视口。
格式决定传输体积。相当画质下 AVIF 约为 JPEG 的一半,WebP 约小四分之一;AVIF 可在 Chrome 85、Firefox 93 和 Safari 16.1(所有平台为 16.4)解码,WebP 自 Safari 14 起在所有引擎可用(Image file type and format guide,developer.mozilla.org)。带 <source type="image/avif"> 的 <picture> 让浏览器不用脚本就选出第一个它能解码的格式。
第一个示例是 CSS,第二个是标记,只有第三个需要脚本。
不会移动布局的 Web 字体
Section titled “不会移动布局的 Web 字体”第一条规则用 swap 加载 Web 字体;第二条声明度量匹配的后备字体,font-family 列表同时写上两者。override 值来自字体的度量(Fontaine 或 Capsize 之类的工具可以计算)。
@font-face { font-family: "Brand Sans"; src: url("/fonts/brand-sans.woff2") format("woff2"); font-weight: 400; font-display: swap;}
@font-face { font-family: "Brand Sans Fallback"; src: local("Arial"); size-adjust: 104%; ascent-override: 92%; descent-override: 22%; line-gap-override: 0%;}
body { font-family: "Brand Sans", "Brand Sans Fallback", sans-serif;}慢网络下页面以缩放到相同度量的 Arial 渲染;brand-sans.woff2 到达后字形改变而什么都不动。品牌字体只是装饰而非必需时,font-display: optional 在慢访问中完全跳过替换。
主图急切加载、高优先级,带 AVIF 源和 JPEG 兜底;画廊图片懒加载并写明尺寸,加载前就预留空间。
<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" fetchpriority="high" decoding="async" alt="产品主图" /></picture>
<img src="/images/gallery-3.avif" width="400" height="300" loading="lazy" decoding="async" alt="画廊照片 3" /><picture> 内的 <img> 仍要带 width 与 height;<source> 元素只负责选 URL。
为脚本生成的图片检测 AVIF 支持
Section titled “为脚本生成的图片检测 AVIF 支持”标记不需要检测,因为 <picture> 会协商。构造 URL 的脚本(canvas 导出、只返回一种格式的 API)可以用一张单像素 AVIF 探测一次并缓存答案。
let avifSupport;function supportsAvif() { if (avifSupport) return avifSupport; if (!('Image' in window) || !('decode' in HTMLImageElement.prototype)) { avifSupport = Promise.resolve(false); // 没有 decode():请求 JPEG return avifSupport; } const probe = new Image(); probe.src = 'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAEAAAABAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQAMAAAAABNjb2xybmNseAACAAIABoAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAAB9tZGF0EgAKCBgABogQEDQgMgkxIABJSElBQkJB'; avifSupport = probe.decode().then(() => true).catch(() => false); // 解码失败:退回 JPEG return avifSupport;}格式未知时 decode() 以 EncodingError 拒绝,catch 把它映射为 false;同一种拒绝也覆盖损坏的文件,所以探测只对已知样本运行。
- font-display(developer.mozilla.org)
- Optimize resource loading with the Fetch Priority API(web.dev)
- Image file type and format guide(developer.mozilla.org)
- 资源提示(preload 与 preconnect)
- Core Web Vitals(LCP、INP 与 CLS)
- 预缓存策略
规范
| 规范 | 状态 |
|---|---|
| 无。 | |