中文字体栈跨平台实践:font-family配方、字重行高与排查

做前端这些年,我改过最久的一行 CSS 不是什么复杂动画,而是一行font-family。西文那一句-apple-system, "Helvetica Neue", Arial, sans-serif抄了十年也没出过事,可一旦换成中文,同样的写法在不同机器上能长出三种完全不同的样子:同样 16px 的正文,在 macOS 上是苹方那种清瘦、收得紧的字面,换到 Windows 上命中微软雅黑,字一下"胖"了一圈,行距显得局促;再换到 Android,又变成思源黑体,笔画粗细、标点宽度、数字宽度全都不一样。最难办的是评审会上有人拿其中一台机器的截图说"就按这个效果来",而你手里的另外两台设备从物理上就做不到。

这篇文章想解决的就是这件事:把中文字体栈写对、写稳、写到跨平台看起来差不多。适合三类人看——写 CSS 的前端、做跨端 App 或小程序或 HTML 邮件的开发者,以及被中英混排、标点挤压、粗体发虚折磨过的排版洁癖。我会讲清楚中文字体栈为什么不能照抄英文那一套,把各平台自带的中文字体家底列清楚,给出四套我自己常年直接抄的font-family配方,再把配套的字号、行高、字重参数和排查链路一并交代。看完你至少能做到一件事:不再靠猜来决定那串字体名怎么写。

1. 中文没有"通用字体栈",同一段 CSS 在三端会长成三样

1.1 西文那一行之所以能通用,是因为它赌对了两件事

英文站点的字体栈之所以常年只用一行就够,背后其实赌了两个前提。第一个前提是字形差异足够小:Helvetica、Arial、Segoe UI、Roboto、San Francisco 这几套无衬线字体,x 高度接近、字宽接近、笔画粗细接近,同一个 16px 下视觉体积差不了多少,用户在哪个平台看到都不会觉得别扭。第二个前提是字重齐全:真正需要加粗、变细、做斜体的时候,系统里总能找到对应的字重文件,浏览器不用自己动手"造"。

这两件事在中文里都不成立。中文字体的字形密度高得多,同样是 16px,汉字的字面基本撑满整个 em 框,而拉丁字母的视觉体积只有 em 框的六七成,所以中文看起来天生比英文"大"、比英文"挤"。再加上各家厂商的黑体在字面率、笔画粗细、收笔方式上的审美取向完全不同,苹方偏细长挺拔,微软雅黑偏饱满圆润,思源黑体介于两者之间但笔画更"直"。这些差异在英文里是可以忽略的噪声,在中文里就是一眼能看出来的画风变化。

1.2 中文字体没法塞进 @font-face,这才是所有问题的源头

西文可以随便@font-face引一个 30KB 的 woff2,所以英文站点的字体栈其实只是"兜底方案",真正生效的往往是那个自定义字体。中文不行:一套覆盖 GB2312 常用字的黑体,woff2 压缩之后也得三五百 KB,全量字形(几万个汉字加符号)轻松上到几 MB 到十几 MB。让用户为了看你一篇文章下载 5MB 字体,等于直接劝退。

于是中文字体栈的本质就变了——它不是"优先用我的字体,失败时兜底",而是"全程都在用别人机器上已有的字体,我只能控制用的顺序"。这就意味着你能做的事情只有两件:第一,把顺序排对,让每个平台都命中它自己最合适的那个字体;第二,字体之外的地方(字号、行高、字重、间距)跟着字体一起调,把差异压下去。

1.3 字重断层和中文化斜体,是两个必须提前知道的事实

微软雅黑这套字体实际上只有三个可用字重:Light、Regular、Bold。你写font-weight: 500或者600,浏览器做的第一件事是"就近匹配",很可能直接给你渲染成 Regular,也就是你的设计里那句"半粗强调"在 Windows 上根本没体现;写font-weight: 800的时候,某些浏览器会拿 Bold 再做一次加粗,笔画就糊在一起了。而苹方有 Ultralight 到 Semibold 六个字重,同样的 600 在 macOS 上是一个非常漂亮的 SemiBold。同一份 CSS,两端一个字重对得上、一个字重对不上,这件事在做设计系统的时候特别容易被忽略。

中文化斜体更麻烦。汉字本身没有斜体形态,font-style: italic在中文上得到的永远是浏览器按大约 12 度硬拗出来的合成斜体,字口变形、笔画错位,观感极差。所以中文里表达"强调"这件事,宁可换字重、换颜色、加左侧色条,也不要用italic。真要禁止浏览器乱来,可以显式写font-synthesis: none,代价是那些"都没有真字重"的情况会退回 Regular 而不是被合成。

2. 把各平台的中文字体家底盘一遍:哪些能写、哪些是坑

2.1 Windows:微软雅黑是主线,但要记住它的变体和老系统

Windows 上的中文字体可用性在 Vista 之后基本稳定下来了:系统 UI 和网页默认栖息在微软雅黑(Microsoft YaHei)上,Windows 10 之后界面用的是一个变体Microsoft YaHei UI,两者字形几乎一样但字距和行高微调过,网页里通常不需要单独写它。老系统(XP 时代)身上是中易宋体 SimSun,这个字体在小字号下笔画细得像断线,屏显效果很差,但因为出现在一些老设备上,宋体系的栈里还是得留着它兜底。

除此之外 Windows 上还常驻着几套字体:黑体 SimHei(比微软雅黑更"方正"、字面更大)、等线 DengXian(Office 2016 之后随装的,偏现代但笔画偏细)、楷体 KaiTi、仿宋 FangSong。等线和楷体在网页里基本不用,但如果你的产品里要做"公文体"或者"手写感"的装饰标题,它们是可以直接调用的现成资源。

一个容易踩的点:微软雅黑的字面率比苹方大,同一font-size下在 Windows 上看起来更饱满。如果你的设计稿是在 macOS 上做的,直接拿到 Windows 上看会显得"字变大了一号",行高必须同步放宽。

2.2 macOS 与 iOS:苹方接管了 2015 年之后的所有中文

macOS 10.11 之后,系统中文默认字体从华文黑体(STHeiti / Heiti SC)换成了苹方-简(PingFang SC)。苹方有六个字重,字面清瘦、字距舒服,是到目前为止我认为最适合屏显正文的中文黑体之一。iOS 上同理,新系统全部是苹方,只有很老的设备会退回 Heiti SC。

除了苹方,mac 上还常驻着冬青黑体简体中文(Hiragino Sans GB),在 10.10 及更早的系统上它是很多网站的中文默认字体,现在虽然不再是主角,但作为一个中间兜底还是很有价值——它的字形比苹方更接近传统黑体。衬线方向有宋体-简(Songti SC)和华文宋体(STSong),装饰方向有楷体-简(Kaiti SC)、华文楷体(STKaiti)、圆体-简(Yuanti SC)。

这里有个很多人不知道的细节:在 macOS 上写-apple-system,浏览器的系统 UI 字体(San Francisco)会自动把中文字符交给苹方渲染。所以你在 mac 上写不写"PingFang SC"效果一样,但一旦你希望显式锁定中文字体(比如你想让中文走冬青黑体而不是苹方),就必须把中文字体名单独写出来。

2.3 Android、Linux 和国产系统:思源黑体阵营,但名字有好几个

Android 从 5.0 之后用的中文黑体是思源黑体体系,字体名通常显示为Noto Sans CJK SC。早期的 Android 用的是一套更细的Droid Sans Fallback,字形质量和现在的差距很大,但留存设备不多。等宽方向上需要特别说明一点:Android 和 Windows 上都没有真正意义上的中文等宽字体,代码块里出现中文,字宽一定是比例宽度,不可能像英文那样对齐到两个字符位。

Linux 桌面最稳的选择是Noto Sans CJK SC,但很多精简发行版不一定装了它,可能只有文泉驿微米黑(WenQuanYi Micro Hei)或者文泉驿正黑(WenQuanYi Zen Hei),所以字体栈末尾一定要把它写上。此外,部分国产手机系统会预装自研的现代黑体,字体名带品牌前缀,字形观感和思源接近,这类字体不用专门写,交给末尾的sans-serif兜底就行。

最后是思源黑体 / 思源宋体的双名问题,这是 Linux 上最容易出问题的地方:

字体家族常见安装名说明
思源黑体Source Han Sans SC与Noto Sans CJK SC同一套字形的两个发布名,发行版装的可能只是其中一个
思源宋体Source Han Serif SC与Noto Serif CJK SC同上
微软雅黑Microsoft YaHei与微软雅黑同一字体的中英文名,建议两个都写
苹方PingFang SC只有英文名,写中文名无效

3. 四套我常年直接抄的 font-family 配方

3.1 通用正文栈:西文优先、中文兜底的分层写法

这是我放在 CSS 变量里、几乎每个项目都用的那一套:

:root { --font-sans: -apple-system, /* macOS / iOS 系统 UI 字体,中文自动落苹方 */ BlinkMacSystemFont, /* 旧版 Chrome 在 mac 上的同一入口 */ "Segoe UI", /* Windows 的拉丁字形 */ "PingFang SC", /* macOS / iOS 中文 */ "Hiragino Sans GB", /* 老版本 macOS 中文兜底 */ "Microsoft YaHei", "微软雅黑", /* Windows 中文 */ "Source Han Sans SC", "Noto Sans CJK SC", /* Linux / Android 中文 */ "WenQuanYi Micro Hei", /* 精简 Linux 兜底 */ sans-serif; } body { font-family: var(--font-sans); }

这套写法的核心逻辑是"分级":第一级是系统 UI 字体入口,负责让拉丁字符拿到最贴合平台的西文字形(mac 上 SF、Windows 上 Segoe UI);第二级以上全部是中文字体,负责在拉丁字体没有汉字字形时接管。浏览器解析字体族的规则是逐字符从前往后找,所以西文字体一定放前面,中文字体一定放后面——顺序反了,中文字体里的拉丁字形会先被命中,结果就是英文变成中文黑体里那种"半宽、偏方正"的拉丁字母,看起来特别业余。

有一点值得强调:"Microsoft YaHei"和"微软雅黑"我习惯两个都写。虽然现代浏览器对中文族名的解析已经没问题,但在一些老旧内核和部分 App 的内嵌 WebView 里,中文族名反而更稳,两个都写上不会有任何副作用。

3.2 标题栈:中文提到前面,字重固定在能对得上的档位

标题的情况和正文相反——标题里中文占比更高,而且往往字数少、字号大,多一次字体回退判断的代价被放大了。所以标题栈我会把中文字体提到前面:

.title { font-family: "PingFang SC", "Microsoft YaHei", "微软雅黑", "Source Han Sans SC", "Noto Sans CJK SC", -apple-system, "Segoe UI", sans-serif; font-weight: 600; }

为什么把中文提前?因为标题里绝大多数是汉字,"PingFang SC"第一个命中,拉丁字符交给它内部的拉丁部分渲染(苹方自带的拉丁字形质量并不差)。更重要的是,把中文放在前面之后,font-weight: 600在苹方上能拿到真正的 SemiBold,在微软雅黑上会掉到 700(Bold)——这是我实测下来最能接受的结果,比掉到 Regular 强得多。如果你的设计对 600 特别敏感,可以按平台写两套值:

.title { font-weight: 600; } @supports (-webkit-touch-callout: none) { /* 走 iOS/macOS 分支时保持 600 */ .title { font-weight: 600; } }

说实话,这种@supports探测字体能力的写法很脆弱,我更多是在设计阶段就定死:标题只用 400 和 700 两档,避开所有中间字重,跨平台表现最稳定。

3.3 代码块栈:接受"中文必然不等宽"这个事实

代码块是唯一一个我必须坦白承认做不到完美的地方:

pre, code, kbd, samp { font-family: ui-monospace, /* 现代浏览器的等宽入口 */ SFMono-Regular, /* 旧 Safari */ "SF Mono", /* macOS 自带 */ Menlo, Monaco, /* macOS 老牌等宽 */ Consolas, /* Windows 自带,屏显最舒服的等宽之一 */ "Liberation Mono", /* Linux */ "Courier New", monospace; font-size: 0.9em; font-variant-east-asian: proportional-width; letter-spacing: 0; }

font-variant-east-asian: proportional-width这一行是这套写法的关键,也是很多人不知道的一招。它让东亚字符按比例宽度渲染,而不是按传统的全角宽度渲染,效果是代码注释里的汉字不会一个占两个字符位、把一整行撑得稀稀拉拉。这个属性在不同浏览器上的支持程度有差异,但即使不生效也不会报错,属于"用了不亏"的类型。

如果你对代码块里的中文对齐有强需求,唯一的正解是引入一套第三方中文等宽字体,比如基于等宽思路制作的"更纱黑体"这类,把它的家族名放在Consolas之后。但要做好心理准备:这套字体动辄十几 MB,必须做子集化,否则首屏会很难看。

3.4 宋体栈和楷体栈:字号一定要抬上去

需要"书卷气"的场景(长文阅读、文章详情、报告导出)我会切到宋体栈:

.prose { font-family: Georgia, "Times New Roman", "Songti SC", "宋体-简", SimSun, "Source Han Serif SC", "Noto Serif CJK SC", STSong, serif; font-size: 17px; line-height: 1.9; }

宋体的问题在于笔画极细,横画只有一两个像素宽,在低分辨率屏或者系统缩放不是 100% 的机器上非常容易糊。所以宋体正文的font-size我从来不敢低于 16px,行高也一定比黑体再抬 0.1 到 0.2。另外 Windows 上的 SimSun 小字号观感明显差于 mac 上的宋体-简,如果你要照顾 Windows 用户,宋体方案最好只在字号足够大的长文场景用,短文本和 UI 一律走黑体。

楷体栈更简单,用在引言、签名、装饰性标题上:

.handwriting { font-family: "Kaiti SC", STKaiti, KaiTi, "楷体", "FangSong", "仿宋", serif; }

这里多留了一个仿宋兜底,因为有些精简系统上楷体缺失,仿宋的相对观感最接近。

4. 字体栈写完只做了一半:字号、行高、字重才是决定观感的另一半

4.1 中文字号行高的经验配比

英文站点正文 16px / 1.5 是通行做法,直接搬到中文上会显得挤得难受。原因前面说过:汉字字面撑满 em 框,视觉高度天然比拉丁字母大,同样的行高下中文的行间距看起来更窄。我这些年固定下来的配比大致是这样:

场景font-sizeline-height备注
长文正文16–17px1.75–1.9黑体取 1.75,宋体取 1.9
列表、卡片描述14px1.6再低汉字会粘在一起
一级标题28–32px1.35中文标题行高别超过 1.4
次级标题18–20px1.45
代码块13–14px1.7中文注释多的话再抬到 1.8
按钮文字14–15px1.2单行,行高只影响垂直居中

另一个容易被忽略的点是font-size的写法。用rem比px更稳,因为用户如果改了浏览器默认字号,rem会跟着放大,而px不会。但绝对不要在嵌套元素上用百分比叠乘font-size: 90%,叠两层之后 16px 变成 12.96px,中文在这个尺寸下笔画开始糊。现代浏览器可以用clamp()做自适应,比如font-size: clamp(15px, 1rem + 0.2vw, 18px),这样窄屏不会小到看不清、宽屏不会大到一行放不下几个字。

4.2 字重选择:宁可只用两档,也别指望中间值

我在做设计系统时定过一条规矩:中文字重只用 400 和 700 两档,特殊情况加一个 300(仅用于超大标题)。理由很实际——你无法保证用户设备上有 500 或 600。苹方有,微软雅黑没有,思源黑体的可变字体版本有但静态版本可能只装了部分档位。一旦你依赖了 600,在缺字重的平台上浏览器要么就近取 500(没有就取 400),要么合成一个假的 SemiBold 出来,两种结果都不可控。

如果真的要用中间字重,配套写一句:

.strong { font-weight: 600; font-synthesis: none; /* 禁止浏览器合成粗体,缺字重时老老实实退回 400 */ }

font-synthesis: none的取舍要讲清楚:它能避免"合成粗体发虚"这个经典问题(合成粗体是在原字形上横向加粗,汉字的横竖笔画会粘连),但代价是缺字重的平台会直接显示为常规体,强调感消失。所以我的做法是——重要信息别只靠字重区分,同时给颜色或者背景,这样即使字重掉了也不会丢失信息层级。

还有-webkit-font-smoothing: antialiased这个老生常谈。它在 macOS 上会让苹方渲染得更细,视觉上更接近设计稿里那种清瘦感;在 Windows 上它完全无效(那边的渲染走的是另一套机制,text-rendering: optimizeLegibility也基本没用)。所以它是一个"只在 mac 上生效的偏方",别指望它解决跨平台观感差异。

4.3 中英混排的间距、断行和标点

中英混排好不好看,字体栈只占三成,剩下七成在这些细节上:

  • 间距:letter-spacing: 0.01em ~ 0.02em是我常用的微调区间,能让中文稍微松一点,看起来不那么密。超过 0.05em 汉字会散架。
  • 断行:overflow-wrap: anywhere应对超长英文串和 URL 撑破容器;中文本身可以在任意字间断行,所以不需要word-break: break-all(那样会把英文单词也切碎)。
  • 标点禁则:line-break: strict可以让浏览器遵守更严格的中文断行规则,避免句号、逗号、右括号出现在行首。这个属性在中文排版里价值很高,但很多人不知道。
  • 中英之间要不要加空格:CSS 层面没有可靠的自动方案,text-autospace的兼容性目前还不行。实际项目里我是在内容侧处理——富文本渲染时用正则在中英文边界插入一个 或者一个带margin的span,或者在编辑规范里直接要求作者手打空格。
  • 全角标点和数字混排:中文全角逗号后面接阿拉伯数字时,视觉上会多出一个空档,这是全角标点的固有宽度导致的,只能靠内容侧统一规范(比如数字一律用半角、标点一律用全角),别指望 CSS 修。

5. 声明了字体却没生效:从 DevTools 到截图的完整排查链路

5.1 先确认字体名有没有写错

这是最高频的原因,而且症状很迷惑人:整个页面的字体都变了,看起来像是"某条规则没生效",其实是字体族名拼错了导致整条链从那一项开始失效。三个检查点:

第一,带空格的族名必须加引号。Microsoft YaHei不加引号在多数浏览器里也能解析,但像Source Han Sans SC这种三段式的名字,不加引号在部分解析器里会被拆成三个独立族名,等于写了三个不存在的字体。养成习惯:只要族名里有空格,一律加双引号。

第二,中英文名双写。"Microsoft YaHei", "微软雅黑"这种写法不冗余,某些内嵌 WebView 只认其中一个。

第三,拼写要照官方写法。字体族名匹配本身不区分大小写,但拼错一个字母就是另一个字体。PingFang SC不能写成Pingfang,Hiragino Sans GB不能漏掉Sans。

5.2 用 Rendered Fonts 面板看"到底用了谁"

Chrome 和 Edge 的 DevTools 里有一个非常实用的面板:选中元素之后,在 Elements 面板右侧的 Computed 标签页拉到最下面,有一块Rendered Fonts,它会明确告诉你这段文字实际是用哪个字体渲染的、每个字体贡献了多少个字形。这是排查字体问题的第一手证据,比猜快一百倍。

如果它显示的是你栈里最后一个sans-serif,说明前面所有字体都没命中;如果显示的是某个你根本没写的字体(比如系统默认的宋体),说明你的 CSS 压根没应用上,去查选择器优先级或者样式被覆盖。还有一个细节:如果它显示某个字体但只贡献了个位数字形,通常是那个字体只提供了拉丁字符,汉字是被后面的中文字体接管的,属于正常现象。

5.3 三端截图对比法:把差异量化出来

我处理跨平台字体问题有一套固定的土办法,很笨但特别有效。写一个测试页,里面放一段固定的对照文本,覆盖中文、英文、数字、中英混排、全角标点、括号、时间格式、货币符号:

<p class="sample"> 中文测试 ABC abc 123 ,。!?()「」【】 15:30 ¥99.00 iPhone 15 Pro Max / Windows 11 / Android 14 </p>

然后在这段文字上分别套用你要验证的字体栈,在 macOS、Windows、Android(真机,不是模拟器)各截一张图,字号统一 16px、行高统一 1.75、宽度统一 600px,把三张图裁成同样尺寸并排放。这么做的价值在于,你能直接看到三件事:同一个字号下三端的字面大小差多少、标点在行首尾的表现差多少、中英混排的间距差多少。有了这三张图,你调font-size和letter-spacing就有了明确的目标值,而不是"我觉得 mac 上好看就这样定"。

5.4 网页字体:中文字体真要引,就得做子集化

如果设计要求必须用一套系统里没有的中文字体(比如某个品牌定制的黑体),那@font-face是唯一的路,但必须做三件事,否则性能会崩:

第一,子集化。按常用字表(3500 常用汉字 + 常用标点 + 拉丁字母 + 数字)切出最小集合,woff2 格式下大概能压到几百 KB。如果内容里有生僻字或者人名地名,要额外补一份扩展字表。

第二,按 unicode-range 切片。把大字体切成若干个按码位区间划分的文件,浏览器只会下载当前页面实际用到的那些片。这个策略对中文特别有效,因为大部分页面用到的汉字其实只有几百个。

第三,font-display: swap加上尺寸补偿。swap让文字先用系统字体渲染,自定义字体下载完再换,避免白屏无字(FOIT)。但切换瞬间会跳动,可以用size-adjust、ascent-override、descent-override把自定义字体的度量调整到和兜底字体接近,跳动会小很多。

@font-face { font-family: "Brand Sans"; src: url("/fonts/brand-sans-common.woff2") format("woff2"); font-display: swap; unicode-range: U+4E00-9FFF, U+3000-303F, U+FF00-FFEF, U+0020-007E; size-adjust: 98%; }

5.5 用户侧还有一堆你控制不了的变量

有些"字体不对"根本不是你的问题,而是用户环境:

现象可能原因处理方向
只有个别用户字体异常浏览器"最小字号"设置或系统字体缩放(如 Windows 125%)用rem布局,避免像素级写死
全部文字变了一种陌生字体用户装了字体替换类插件或系统主题改了默认字体无解,靠字体栈末尾兜底
粗体发虚、笔画粘连合成粗体font-synthesis: none或改用 700
中文标点挤成一团全角标点 + 窄容器 +text-align: justify调line-break、放宽容器、关掉两端对齐
首屏文字闪一下才变正常网页字体加载 + 没做度量补偿font-display: swap+size-adjust

这张表我能背下来,是因为每一个坑我都至少踩过一次。其中"最小字号"那个最阴——你按 12px 设计的小字备注,在很多用户机器上会被强制放大到 14px 甚至 16px,布局直接错位。这里没有完美的解法,只能设计时就不依赖 12px 以下的字号。

6. 几个反复验证过的结论和长期维护习惯

6.1 别在 body 上写死单一中文字体

我见过太多项目直接body { font-family: "Microsoft YaHei"; },在 Windows 上确实好看,但 mac 用户、Android 用户、Linux 用户看到的全是回退字体。正确做法永远是把字体栈定义在:root的 CSS 变量上,body只引用变量。这样后续要加字体、换顺序、按平台打补丁,都只改一处。

:root { --font-sans: /* 一长串 */; --font-serif: /* 一长串 */; --font-mono: /* 一长串 */; } body { font-family: var(--font-sans); } .prose { font-family: var(--font-serif); }

变量化的另一个好处是能在主题切换、暗色模式、导出场景里直接换一整套字体,不用去找散落在各处的font-family。

6.2 断点要跟着字体走,不要跟着设备宽度走

响应式里常见的错误是"768px 以下字号降到 14px",但真正该决定字号的是这个尺寸下用什么字体、一行能放多少汉字。中文一行 25 到 35 个字是阅读舒适区,超过 40 个字眼睛回扫会累,低于 15 个字断行太频繁节奏乱。所以我会先按容器宽度算字数:容器 600px / 字号 16px ≈ 37 字,偏多,把字号提到 17px 或者把容器收到 560px;容器 320px / 字号 14px ≈ 22 字,偏少,行高抬到 1.75 补回来。这个算法比记断点有用得多。

6.3 设计稿里的字体名不等于生产环境的字体名

设计稿上写着"思源黑体 Medium",你不能直接往 CSS 里写font-family: "思源黑体"。生产环境要写的是"Source Han Sans SC", "Noto Sans CJK SC",因为大多数系统装的是后者。同理设计稿写"苹方 中黑",你要翻译成"PingFang SC"加font-weight: 500,并且提前想好这个 500 在 Windows 上会掉成什么。交接的时候我会把"设计稿字体名 → CSS 字体栈 → 各平台实际字重"做成一张对照表给设计,避免来回返工。

6.4 真机验证这件事没有捷径

模拟器看字体是最不可靠的,因为它用的还是宿主机的字体系统。Android 模拟器永远显示宿主机上的字体,iOS 模拟器在某些版本上和真机的中文字体渲染也有差异。我吃过一次亏:在模拟器上看着完美的行高,到真机上因为系统字号缩放偏了一整行。从那以后,只要涉及字体栈的改动,我一定在至少三台真机上看一眼:一台苹果设备、一台 Windows 机器、一台安卓机。这个成本远低于上线后被人截图吐槽。

最后分享一个小习惯:我会在项目里放一个fonts-test.html,里面就是前面那段对照文本,把项目里所有字体变量各套一遍,一行一个。改字体栈的时候打开这个页面,三端各过一遍,五分钟能看出所有问题。这个东西比任何文档都管用,尤其是接手别人的项目、想搞清楚"这套字体栈到底在哪些平台真的生效"的时候。