font-display 实测 26 个首页:936 条 @font-face 里,165 条没声明它

font-display 决定字体没下载完时浏览器先显示什么,而我们在这批首页的 936 条 @font-face 规则里,发现 165 条根本没声明它。6 个站用 Google Fonts,只有 1 个对它做了 preconnect。每个数字都从落盘 CSS 重算。

抓取与收录6 分钟读完1477 次阅读
font-display 实测 26 个首页:936 条 @font-face 里,165 条没声明它

实测 · 2026-09-20 · 26 个首页 · 936 条 @font-face · 单次抓取

样本与口径:沿用本系列自 2026-08-15 起的同一批 30 个站,2026-09-20 每个首页请求一次,桌面 Chrome UA、不执行 JavaScript。然后取每个首页链接的样式表与内联样式,逐块解析 @font-face。下面每个数字都由落盘的 HTML / CSS 重算,没有一个是手输的。

这里说的 font-display 是 CSS 里那个描述符,不是「装饰字体」(display font)—— 中文下拉里这两层意思混在一起,先点破。它决定字体还没下载完时浏览器先显示什么。26 个首页里我们数出 936 条 @font-face 规则,其中 165 条、也就是 17.6%,根本没声明 font-display。23 个有网页字体的站里,有 14 个至少躺着一条这样的规则。它只是一行,六分之一的规则却把它交给了浏览器默认。

我们是怎么测的

每个首页抓一次、存下最终下发的 HTML,然后把页面里每一条 rel="stylesheet" 链接和每一段内联 <style> 收集起来,每站最多取 30 份样式表,再逐块遍历 @font-face,看这一块内部有没有 font-display 声明、以及它的字体地址来自哪个域。只有写在这一块里的才算数;CSS 别处写了 font-display 不能算到一条没写它的规则头上。

curl -s -L --compressed \
  -A "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" \
  https://example.com/ -o page.html

python3 -c "
import re, urllib.request
html = open('page.html').read()
css = ''
for href in re.findall(r'<link[^>]+stylesheet[^>]+href=[\"\']([^\"\']+)', html):
    if href.startswith('http'):
        try: css += urllib.request.urlopen(href, timeout=20).read().decode('utf-8','replace')
        except Exception: pass
faces = re.findall(r'@font-face\s*\{[^}]*\}', css, re.S)
with_display = [f for f in faces if re.search(r'font-display\s*:', f, re.I)]
print(len(faces), 'faces,', len(with_display), 'with font-display')
"

这套口径有两件事看不到,而且都往同一个方向偏。它读的是页面链接或内联的样式表,所以写在脚本注入的样式表里的字体它看不见 —— 面板上有一个站预载了字体文件却显示 0 条 @font-face,正是这个症状。另外它数的是声明,不是渲染:一条声明了 font-display 的规则,照样可能被层叠里后面的规则盖掉。

26 个首页各有多少条 @font-face

分布很不均。一个建站平台一家就生成了 936 条里的 378 条,三个站一条网页字体都没有,其余的落在 3 到 113 之间。下表是整个面板,按条数排序。

站点规则已声明缺声明
framer.com378248130
discord.com1131121
arstechnica.com95923
railway.com45450
wired.com37370
bbc.com35314
notion.com35350
ycombinator.com29281
slack.com26179
supabase.com24213
theverge.com23194
substack.com22220
mozilla.org1082
techcrunch.com10100
nextjs.org871
vercel.com871
shopify.com880
github.com752
netlify.com770
cloudflare.com660
figma.com422
stripe.com330
webflow.com312
linear.app000
reddit.com000
wikipedia.org000

那三个零不是坏页。它们用的是系统字体栈 —— 不用下载、也不会闪,这跟 样式表那篇实测里同一种「干脆不加载」的选择是一回事。真正值得停一下的是 slack.com:26 条规则里 9 条没写描述符,而它的第一印象整个就是文字。

font-display 已经声明的那 771 条,写的是什么值

在 771 条声明了 font-display 的规则里,一个取值压倒性地多。swap 是主流,而那几个「宁可稳一点、也不要文字闪」的取值很少见。

取值规则数加载时的表现
swap612先显示兜底字体,再替换
fallback114极短阻塞,之后替换
block43阻塞期内文字不可见
optional2不替换,慢就直接不用
未声明165交给浏览器默认

MDN 用两个时段来定义这几个值:block period(用这个字体的文字被渲染成不可见的时段)与 swap period(先用兜底字体渲染、之后再替换的时段)。swap 给的是极短的 block period 加无限的 swap period,所以文字几乎不会长时间不可见;block 给的是较短的 block period 加无限的 swap period,正是它制造了经典的「文字先白一下」。

font-display 是那一行决定:一个慢字体让你付出的是文字不可见,还是可见的重排。

字体从哪来,谁做了 preconnect

自托管是这批面板的默认。我们解析出的 1,069 条字体地址里,427 条指向本站域名,其余大多指向站点所基于的平台 CDN。Google Fonts 出现在 6 个站上,而这 6 个站里只有一个对它做了 preconnect。

来源地址数说明
本站域名427自托管字体文件
framerusercontent.com308Framer 自己的 CDN
fonts.gstatic.com108Google Fonts 文件域
website-files.com108Webflow 的 CDN
bbci.co.uk30BBC 的字体 CDN
substackcdn.com10Substack 的 CDN
内联 data URI7字体嵌在 CSS 里
fonts.googleapis.com6Google Fonts 样式表域

preconnect 那一列才是值得停的地方。26 个站里有 6 个从 Google Fonts 拉字体样式表 —— 那是一个浏览器必须新开连接的第三方源 —— 而其中 5 个没有告诉浏览器提前去开。唯一做了的那个 railway.com,也正好是唯一在 Google Fonts 链接上带了 display=swap 的站,所以它那 45 条规则全部声明了描述符。Google 自己的字体指南就建议对第三方字体源做 preconnect;在这批面板上它是例外。

这对你自己的页面意味着什么

有用的动作不是给所有规则都补一个值,而是找出那些「渲染着你在意的文字」的规则,让每一条都有个交代。

  1. 用上面那条命令把所有 @font-face 列出来,标出没写 font-display 的那些 —— 它们正跑在浏览器默认上。
  2. 凡是「访客在字体到达前就会读到的文字」所在的规则,补上 font-display: swap。它是让文字保持可见的那个值,也是这批面板多数站的选择。
  3. 如果你从第三方源拉字体样式表,在 head 里给它加一条 preconnect。这里 6 个站用 Google Fonts、只有 1 个做了预连接,等于把现成的时间留在了桌上。
  4. 别预载每一个字体文件。26 个站里只有 11 个预载了字体,而一条跟主图抢带宽的 preload,代价可能比它省下的还大。
  • 逐条决定 font-display,判据是那段文字是内容还是装饰。
  • 别让渲染你第一行大标题的那条规则吃默认值。默认不是「没有影响」,是一个你没做过的选择。

有一个前提贯穿全文:我们数的是 CSS 声明了什么,不是浏览器据此做了什么。我们没有真的渲染这些页面,所以答不出哪几个站实际闪了白字。想知道爬虫这种不做渲染的客户端到底收到了什么,那是另一个问题,AI 爬虫可达性检查能让你看到。

常见问题

你们是怎么测的?

2026-09-20 每个首页请求一次,桌面 Chrome UA、不执行 JavaScript、跟随跳转,响应按字节落盘。然后取每个页面链接的样式表(每站最多 30 份),逐块解析 @font-face,记录这一块里有没有 font-display 描述符。面板是本系列一直在用的同一批 30 个域;medium.com、stackoverflow.com、canva.com、nytimes.com 对我们的客户端返回 403,剔除后剩 26 个。

font-display 是排名因素吗?

不是,本文也没有这么说。它改变的是字体加载期间读者看到什么,最多算 Core Web Vitals 的问题,而排名影响我们这次没量。站得住的版本更窄:它在「文字不可见」和「可见地替换一次」之间做选择。

没写这个描述符,文字就一定看不见吗?

不一定。没有 font-display 时浏览器用默认值,MDN 列的初始值是 auto,由用户代理自己决定。有的浏览器表现得像 block,有的像 swap。问题在于你没做选择,结果取决于读者的浏览器。

字体要不要自托管?

性能上的账没有听起来那么清楚。Google 的字体指南一方面说自托管省掉了第三方连接,另一方面也引了 Web Almanac 的发现:实际测下来用第三方字体的站渲染更快。我们能补的是一个计数 —— 在这批面板上自托管是常态,而那 6 个用 Google Fonts 的站大多省掉了那一步。

为什么有一个站有 378 条规则?

因为它是一个建站平台。Framer 会按字重、字形和字符子集各生成一条 @font-face,一个字体家族就能变成几十条规则。这本身不是问题,但也说明规则条数不能当成质量分来读。

你们没测什么?

渲染,以及加载之后由 JavaScript 注入的任何东西。我们没测任何字体的到达速度、体积,也没测页面在替换时有没有位移。本文讲的全是 CSS 里声明了什么。

font-display 实测 26 个首页:936 条 @font-face 里,165 条没声明它