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

实测 · 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.com | 378 | 248 | 130 |
| discord.com | 113 | 112 | 1 |
| arstechnica.com | 95 | 92 | 3 |
| railway.com | 45 | 45 | 0 |
| wired.com | 37 | 37 | 0 |
| bbc.com | 35 | 31 | 4 |
| notion.com | 35 | 35 | 0 |
| ycombinator.com | 29 | 28 | 1 |
| slack.com | 26 | 17 | 9 |
| supabase.com | 24 | 21 | 3 |
| theverge.com | 23 | 19 | 4 |
| substack.com | 22 | 22 | 0 |
| mozilla.org | 10 | 8 | 2 |
| techcrunch.com | 10 | 10 | 0 |
| nextjs.org | 8 | 7 | 1 |
| vercel.com | 8 | 7 | 1 |
| shopify.com | 8 | 8 | 0 |
| github.com | 7 | 5 | 2 |
| netlify.com | 7 | 7 | 0 |
| cloudflare.com | 6 | 6 | 0 |
| figma.com | 4 | 2 | 2 |
| stripe.com | 3 | 3 | 0 |
| webflow.com | 3 | 1 | 2 |
| linear.app | 0 | 0 | 0 |
| reddit.com | 0 | 0 | 0 |
| wikipedia.org | 0 | 0 | 0 |
那三个零不是坏页。它们用的是系统字体栈 —— 不用下载、也不会闪,这跟 样式表那篇实测里同一种「干脆不加载」的选择是一回事。真正值得停一下的是 slack.com:26 条规则里 9 条没写描述符,而它的第一印象整个就是文字。
font-display 已经声明的那 771 条,写的是什么值
在 771 条声明了 font-display 的规则里,一个取值压倒性地多。swap 是主流,而那几个「宁可稳一点、也不要文字闪」的取值很少见。
| 取值 | 规则数 | 加载时的表现 |
|---|---|---|
| swap | 612 | 先显示兜底字体,再替换 |
| fallback | 114 | 极短阻塞,之后替换 |
| block | 43 | 阻塞期内文字不可见 |
| optional | 2 | 不替换,慢就直接不用 |
| 未声明 | 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.com | 308 | Framer 自己的 CDN |
| fonts.gstatic.com | 108 | Google Fonts 文件域 |
| website-files.com | 108 | Webflow 的 CDN |
| bbci.co.uk | 30 | BBC 的字体 CDN |
| substackcdn.com | 10 | Substack 的 CDN |
| 内联 data URI | 7 | 字体嵌在 CSS 里 |
| fonts.googleapis.com | 6 | Google Fonts 样式表域 |
preconnect 那一列才是值得停的地方。26 个站里有 6 个从 Google Fonts 拉字体样式表 —— 那是一个浏览器必须新开连接的第三方源 —— 而其中 5 个没有告诉浏览器提前去开。唯一做了的那个 railway.com,也正好是唯一在 Google Fonts 链接上带了 display=swap 的站,所以它那 45 条规则全部声明了描述符。Google 自己的字体指南就建议对第三方字体源做 preconnect;在这批面板上它是例外。
这对你自己的页面意味着什么
有用的动作不是给所有规则都补一个值,而是找出那些「渲染着你在意的文字」的规则,让每一条都有个交代。
- 用上面那条命令把所有
@font-face列出来,标出没写font-display的那些 —— 它们正跑在浏览器默认上。 - 凡是「访客在字体到达前就会读到的文字」所在的规则,补上
font-display: swap。它是让文字保持可见的那个值,也是这批面板多数站的选择。 - 如果你从第三方源拉字体样式表,在 head 里给它加一条
preconnect。这里 6 个站用 Google Fonts、只有 1 个做了预连接,等于把现成的时间留在了桌上。 - 别预载每一个字体文件。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 里声明了什么。


