CSS 与布局 / 字体里的隐藏开关 / unicode-range:按字符切字体 待审核 13 / 18
加餐 · unicode-range

unicode-range:按字符切字体

font-family: "Sliced" 看似一款字体,可以是好几张 @font-face 切片:每条 @font-faceunicode-range 声明「我只负责这段码位」,浏览器排版时逐字符查码位、挑对应的切片。于是同一个 family 名字下,Latin 字母走 Georgia、汉字走 PingFang SC——不写一行 JS,中西混排各取所需。这也是 Google Fonts 把一个 CJK 字体拆成上百个 woff2 按需下载的底层机制。

图 1 · 按 unicode-range 切片后各分片的下载情况。可改文本内容,观察哪些分片被真正请求。

1 · unicode-range 的写法

写法 含义
U+4E2D 单个码位(「中」)
U+4E00-9FFF 区间:CJK 统一表意文字基本块
U+4?? 通配:? 代表任意十六进制位,等于 U+400-4FF
U+0-FF, U+2014 逗号分隔可列多段,一条 @font-face 管多块码位

切片静默失败:某条 @font-face 的 src(如 local("PingFang SC"))在当前系统不存在时,该切片的字符自动落到 font-family 列表的下一项,不会报错——上方 readout 实测了本机两款 local 字体是否在。

2 · fallback 本来就是逐字符的

不必用 unicode-rangefont-family 列表的回退就不是整段切换、而是 per-character 的:浏览器逐个字素簇(基字加其组合符号,须由同一款字体渲染)从列表第一项开始找,由第一个含有该字形的字体渲染。此处的判据是有效字符表——unicode-range 声明的区间与字体自身字符表的交集,因此即便区间声称覆盖,字体缺该字形照样落到下一项。Georgia 只有 Latin 字形,于是「Georgia, serif」里的中文逐字穿过 Georgia 落到 serif——一行字里两款字体共存是常态,unicode-range 只是把这套机制变成可主动声明的。

图 2 · 同一行文字在 font-family 回退链下的逐字命中情况,可切换字体列表观察哪一款渲染了哪些字符。

真实用途:Google Fonts 的按 subset 拆包。CJK 字体动辄十几 MB,全量下载不现实——Google Fonts 返回的 CSS 里,一款中文字体(如 Noto Sans SC)被拆成上百条 @font-face,每条一个小 woff2 切片 + 一段 unicode-range(按字频聚类分块)。浏览器只在页面真的用到某切片里的字符时才发起下载,首屏往往只拉两三个百 KB 级的小文件。拉丁字体也同理:latin / latin-ext / cyrillic 各是一条切片,法语页面不会为西里尔字母多付一个字节。

3 · 相关链接

  • unicode-range · MDN——developer.mozilla.org。@font-face 描述符的精确语法:单码位 / 区间 / 通配 ?,以及「只有用到该范围字符才下载」的加载行为。
  • @font-face src 与 local() · MDN——developer.mozilla.org。src 描述符:local() 引用本机已装字体、url() 加载 webfont,以及多候选的回退顺序——本页两张切片全靠 local()。
  • Reduce web font size——web.dev。webfont 瘦身实战:subsetting 与 unicode-range 怎么配合,Google Fonts 按 subset 拆 woff2 的工程背景。