中文字体font-family字体栈:跨平台写法与排查
做前端这些年,被问得最多的问题之一就是:“这页面在我电脑上明明挺好看的,怎么到别人那儿就散架了?”十次里有八次,根子在font-family这一行。中文字体 font-family 的常用列表网上一搜一大把,抄回来往 CSS 里一贴,看起来万事大吉,实际上手才发现:Windows 上跑的是雅黑,macOS 上换成了苹方,安卓手机又变成思源黑体,三种字体的字面大小、字重粗细、标点位置全对不上,原本调好的行高在某个平台上显得特别挤,甚至同一个按钮,在两端文字宽度差出十几个像素。
这篇东西不想只给你一串复制粘贴的中文字体列表就完事。我会把中文 font-family 该怎么写、为什么这么写、写完还要配什么参数、线上出了问题往哪儿查,一层层拆开讲。适合谁看:刚接手项目、被设计稿和线上效果不一致折磨的前端;做 H5 和小程序的移动端开发;还有在自己站点上折腾排版、希望中文读起来更舒服的独立开发者。看完你手里应该能留下一套能直接落地的字体栈,外加一套遇到问题能自己定位的排查思路。
1. 字体栈的本质:一份写给系统的备选名单
1.1 只写一个字体名,到底会出什么事
很多人写font-family: "Microsoft YaHei";就收工了,觉得反正目标用户都是 Windows。问题在于,这行声明的语义是“如果能找到微软雅黑就用它,找不到就用浏览器默认字体”。一旦用户是 macOS、iOS、安卓,或者 Windows 上把这字体卸载了、用了精简版系统,浏览器就直接掉进默认字体兜底,而各浏览器默认中文字体差异极大:有的给你宋体,有的给你黑体,字面高度、笔画粗细全不一样,你按雅黑调的行高立刻失控。
更隐蔽的是第三种情况——字体名写错但不报错。CSS 对font-family里的名字是宽容的,拼错一个字母,不会报语法错误,只是这条匹配失败,静默回退。于是你看着自己的代码“明明写了”,页面上却不是那个字体,来回怀疑人生。我见过最典型的是把"PingFang SC"写成"PingFangSC",少一个空格,整条在 Mac 上就废了。
所以字体栈的第一价值不是“指定一个好看的中文字体”,而是声明一套优先级明确的回退策略,把“最坏情况”也控制在可接受范围内。这条思路跟数据库连接串里配多个备用节点是一个逻辑:能用最好的就用,用不了退而求其次,最后一档必须是个必然存在的兜底。
1.2 匹配顺序、引号规则与兜底的那个通用族
font-family的值是一个用逗号分隔的列表,浏览器从左到右逐个尝试,第一个在当前系统里能找到的字体生效,后面的全部忽略。这里有几个必须记住的硬规则。
带空格的字体名必须加引号。Microsoft YaHei、PingFang SC、Source Han Sans SC这类名字,不加引号在很多解析器里会被当成多个独立标识符,导致匹配失败。单字名比如SimSun、SimHei技术上加不加都行,但我的习惯是统一加引号,省得日后改名字忘了。
列表最后应该落在一个 CSS 通用字体系列上,也就是serif、sans-serif、monospace、system-ui这几个。它们不是具体字体,而是“让浏览器按自己对这个类别的默认设定来选”。这一步的意义在于:哪怕前面所有具体字体全部落空,浏览器至少还能给你一个符合类别的合理选择,而不是随机乱掉。少了这一档,等于把最后一道保险丝也拆了。
/* 有兜底的写法 */ font-family: "PingFang SC", "Microsoft YaHei", sans-serif; /* 没有兜底,全部落空时行为不可控 */ font-family: "PingFang SC", "Microsoft YaHei";还有个小细节:中文字体名建议用英文名(PostScript 名或族名),比如写"Microsoft YaHei"而不是"微软雅黑"。虽然现代环境两者都能识别,但在一些老版本环境、部分内嵌 WebView,以及经过构建工具转义之后,中文名偶尔会出现编码识别问题。稳妥做法是英文名为主;如果你确实想兼容极老环境,也可以两个都写,"Microsoft YaHei", "微软雅黑",代价是列表变长、可读性下降。我个人现在的项目一律只写英文名。
1.3 各平台手里的牌面盘点
写字体栈之前,得先知道每个平台默认有什么牌。这份认知是后面所有选择的基础。
Windows 这边,从 Win7 到 Win11 大致稳定的是:SimSun(宋体,老默认)、SimHei(黑体)、Microsoft YaHei(微软雅黑,Vista 之后的事实标准)、Microsoft YaHei Light(Win8 起)、KaiTi(楷体)、FangSong(仿宋)、NSimSun(新宋体)。Win10 之后还多了DengXian(等线)。需要留意的是,Microsoft YaHei只提供 Regular 和 Bold 两个字重,Light 是独立的另一个字体族名,这点后面讲字重时会重点说。
macOS 和 iOS 这一侧,PingFang SC(苹方-简)是 Yosemite 之后的中文默认字体,字重齐全,从 Ultralight 到 Semibold 共六档。老系统上还有Hiragino Sans GB(冬青黑体简)、STHeiti(黑体-简)作为历史遗留。宋体系是Songti SC,楷体是Kaiti SC,圆体是Yuanti SC,仿宋是STFangsong。
安卓的情况最杂。原生安卓从Noto Sans CJK SC(即思源黑体的 Noto 版本)走起,各家定制 ROM 又各自换过字体,同品牌不同年份的机型都可能不一样。所以移动端的中文栈里,具体字体名的命中率其实很低,sans-serif这个通用族反而是主力。
注意:判断字体是否真的生效,别靠肉眼。打开开发者工具,在 Elements 面板选中元素,看 Computed 里的 Rendered Fonts,那里会明确写出实际参与渲染的字体名。这一步能省掉大量猜测。
2. 中文字体常用清单:按风格拆家族
2.1 黑体家族:正文与界面的默认答案
黑体是笔画粗细均匀、没有装饰性衬线的字体类别,中文界面设计里它是绝对主力。原因很实在:汉字笔画密集,黑体的字形结构简单,在小字号下不容易糊成一团,屏幕阅读的清晰度远好于宋体。所以在正文、按钮、表单、导航这些地方,默认就该用黑体。
桌面端跨平台黑体栈,一个比较稳的组合是这样的:
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", "Source Han Sans SC", "Noto Sans CJK SC", sans-serif;-apple-system是 Safari 的私有值,BlinkMacSystemFont是 Chrome 在 mac 上早期的私有值,现在两个基本可以只留system-ui,但为了兼容老版本,很多项目还是照写。后面的顺序遵循“先用系统自带、再用开源跨平台、最后兜底”的原则:苹果系在前,Windows 系居中,思源/Source Han 这一档放在后面是因为它虽然质量高,但桌面端往往没装,装在移动端或打包进项目的场景才用得上。
需要单独提的是system-ui这个值。它直接映射到操作系统的 UI 字体,在 mac 上是苹方,在 Windows 上是雅黑(较新版本),在 Android 上是 Roboto 加系统中文字体。用它的好处是永远贴合系统观感,坏处是你无法预测具体是哪一款,跨端一致性会差一些。做通用站点我倾向用它打头,做需要严格视觉统一的品牌站就避开。
2.2 宋体家族:长文阅读与文艺调性
宋体的特征是横细竖粗、笔画末端有装饰角,是传统印刷正文的字体。在屏幕上它的适用场景其实挺窄:小字号下笔画容易断、发虚,尤其是早期 Windows 的点阵宋体,在 12px 附近观感很差。但对长文阅读、文化类内容、需要“印刷感”的排版,宋体比黑体更有味道。
推荐的宋体栈:
font-family: "Songti SC", "STSong", "SimSun", "Noto Serif CJK SC", "Source Han Serif SC", serif;macOS 的Songti SC有多个字重,屏幕渲染质量不错;Windows 上退到SimSun,只能靠放大字号和调行高来弥补;再到后面就是思源宋体。这里要注意,SimSun在 Windows 上默认会走点阵渲染,13px 以下字形会变得非常生硬,如果宋体是主要正文,字号建议不低于 16px,行高不低于 1.8。
宋体的另一个坑是中英文混排时的视觉落差。宋体汉字笔画细,而很多无衬线英文字体笔画粗,两者混在一起中文字会显得“虚”。解决办法是英文字体也选一款衬线体搭配,比如 Georgia、Times New Roman,让整体调性一致。
2.3 楷体、仿宋、圆体与其他特色字体
楷体、仿宋、圆体、隶书这些,属于“用对了加分、用错减分”的类型。楷体适合引文、书信体、儿童内容;仿宋适合公文感的标题;圆体适合亲子、休闲、活动页。它们的共同问题是跨平台可用性差:KaiTi在 Windows 有,macOS 对应的是Kaiti SC,Linux 上大概率直接缺失。所以这类字体绝不能作为正文,只能在标题、局部文案上用,并且必须准备好回退到宋体或黑体。
/* 楷体场景:先苹果后 Windows,最后回退到稳妥的宋体 */ font-family: "Kaiti SC", "STKaiti", "KaiTi", "楷体", "Songti SC", "SimSun", serif;圆体还有个额外注意点:字形饱满,同样字号下视觉面积比黑体大,需要把字号适当收小、行高适当加大,否则整段文字会显得拥挤。这类细节光看字体名是发现不了的,必须实际换上去对一遍。
2.4 英文字体与中文的排布顺序
这是最容易翻车、又最容易被忽略的地方。一条字体栈里既有英文字体又有中文字体时,顺序决定了英文字体能不能生效。浏览器的匹配逻辑是逐个字符去找的:对于某个字母,它按列表顺序找第一个包含该字符的字体。所以正确做法是把英文字体放前面,中文字体放后面。
/* 正确:字母先匹配 Helvetica/Arial,汉字跳过它们落到苹方 */ font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Microsoft YaHei", sans-serif; /* 错误:中文字体在前,字母也会被中文字体里的拉丁字形接管 */ font-family: "PingFang SC", "Helvetica Neue", Helvetica, Arial, sans-serif;第二种写法看起来也能用,实际是把所有拉丁字母交给苹方自带的西文字形渲染。苹方的西文部分设计得不算差,但跟周围设计稿里用 Helvetica 的效果肯定有出入,数字更是宽窄不一。而雅黑自带的西文部分质量就比较一般了,尤其是老版本的雅黑,数字和高矮胖瘦都很别扭。所以数字、英文、符号统一交给专门的西文字体,是国内前端排版的通用做法。
再说数字。涉及金额、表格、计数器,我一般会额外要求等宽数字:
font-variant-numeric: tabular-nums;默认情况下很多字体的数字是比例宽度,1窄、8宽,表格里一列数字对不齐,视觉上非常脏。tabular-nums强制所有数字等宽,行高稳定、纵向对齐。这个特性对思源黑体、苹方、雅黑都支持。
3. 可直接抄的场景化字体栈清单
3.1 通用正文型:一套吃遍大部分页面
这是我最常用的一套,适合后台系统、内容站、企业官网的正文区域。它在四大平台上都能命中一个质量不错的黑体,且英文字体有专门的兜底。
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", "Helvetica Neue", Helvetica, Arial, "Noto Sans", "Source Han Sans SC", sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; font-size: 15px; line-height: 1.7; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; }几个选择理由要说明白。"Segoe UI"排在苹果系之后,是因为它是 Windows 的系统 UI 字体,西文观感比雅黑自带的西文更好;如果机器上没有它(比如某些精简版),会继续往后落到 Helvetica 或 Arial。结尾的Apple Color Emoji和Segoe UI Emoji是为了让 emoji 以彩色字形渲染,而不是被中文字体里的单色符号接管——不写这两个,很多系统上的 emoji 会变成黑白线条图标。
-webkit-font-smoothing: antialiased这一行有争议,它让 mac 上的字体渲染变细,但也会让细体字在浅色背景上显得发虚。我的经验是:浅色背景、字号 14px 以上,开着观感更精致;深色背景建议关掉或用subpixel-antialiased,否则字会“薄”得看不清。
3.2 标题展示型:拉开层级靠字体本身
标题和正文用同一个字体栈不是不行,但视觉层级全靠字号和字重撑,容易单调。标题这一层可以做两件事:换字体风格,或者用更重的字重。
/* 稳重一点的标题:只用粗字重,不换字体 */ .title-strong { font-family: "PingFang SC", "Microsoft YaHei", sans-serif; font-weight: 600; letter-spacing: 0.01em; line-height: 1.3; } /* 有调性的标题:走宋体或衬线体 */ .title-serif { font-family: "Songti SC", "STSong", "SimSun", "Noto Serif CJK SC", "Source Han Serif SC", serif; font-weight: 700; letter-spacing: 0.02em; line-height: 1.35; }标题加一点正字距(0.01em到0.03em)是中文排版的常规操作,汉字方块感强,字距为 0 时大字号的笔画容易显得“粘”。但正文不要加太大字距,超过0.05em中文阅读节奏会被打乱。
这里必须提醒字重的问题:font-weight: 600在苹方上能命中 Semibold,在思源黑体上能命中 Semibold,但在微软雅黑上只有 Regular 和 Bold 两档,浏览器会挑最接近的 Bold,或者在某些情况下做合成加粗。所以标题字重我给的建议是:跨平台统一的场景用 700,只在苹果系为主的场景用 600。
3.3 数字、金额与代码:别让中文接管拉丁字符
表格里的金额、统计数据、订单号,这些内容对字形一致性要求最高。我的做法是把它们单独抽成一套字体栈,并且明确让数字字体排在前面。
.num { font-family: "SF Mono", "Segoe UI Mono", "Roboto Mono", "DIN Alternate", Helvetica, Arial, sans-serif; font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; letter-spacing: 0.02em; }代码块则是另一回事,纯等宽场景用系统等宽字体就够了,但要注意中文注释在里面能不能正常显示:
pre, code, kbd, samp { font-family: "SFMono-Regular", Menlo, Consolas, "Liberation Mono", "Courier New", "PingFang SC", "Microsoft YaHei", monospace; font-size: 0.875em; }注意结尾同时保留了monospace通用族和中文字体。为什么中文字体要写在monospace之前?因为纯monospace在部分系统上会把中文也交给等宽字体渲染,导致汉字宽度是英文的两倍多,代码里的中文注释会撑出很难看的空格。把具体中文字体插在monospace前面,汉字能落到一个正常宽度的字形上。
3.4 移动端与小程序精简栈
移动端的字体选择权基本不在开发者手里,写太长的栈没意义,反而增加解析负担。我一般在移动端项目里压缩到三四项:
/* 移动端正文 */ font-family: -apple-system, "PingFang SC", "Helvetica Neue", "Microsoft YaHei", sans-serif; /* 安卓 WebView 兼容补充 */ font-family: system-ui, -apple-system, "PingFang SC", "Noto Sans CJK SC", "Microsoft YaHei", sans-serif;移动端有个必须知道的事实:安卓上的中文字体命中率极低,你写不写Noto Sans CJK SC差别不大,最终呈现取决于机型和系统版本。这意味着不要指望在安卓上做到像素级的中文字体统一,能做的是保证字号、行高、字距在视觉上大致对齐。我通常会给移动端单独定一套字号(正文 15–17px,行高 1.6),而不是沿用桌面端的数值。
小程序这边更特殊,它的渲染层和 WebView 不完全一致,自定义字体的加载限制更多,font-family里的非系统字体经常直接失效。我踩过的一次坑是在小程序里引用了一个本地打包的思源黑体,开发者工具里显示正常,真机上一片默认字体——原因是渲染层不支持那种加载路径。后来改成系统字体栈才稳。
4. 光有字体栈还不够:字号、字重、行高的联动
4.1 伪粗体:中文字重最容易翻车的地方
这是我认为最值得单独拎出来讲的一点。CSS 规范里,当你请求的字重在当前字体中不存在时,浏览器会做两件事之一:一是在已有字重里找最接近的,二是用算法把细的字形“描粗”,也就是合成加粗。英文上合成加粗勉强能看,中文上就是灾难——汉字笔画本来就密,描粗之后笔画粘连,边缘发毛,看起来像糊了一层墨。
微软雅黑就是典型受害者。它只有 Regular 和 Bold,你写font-weight: 500或者font-weight: 600,在 Windows 上大概率拿到的是合成出来的假粗体。解决办法有两条:
/* 方案一:直接禁止合成,宁可回退到真实字重 */ body { font-synthesis: none; } /* 方案二:只使用字体确实提供的字重 */ .title { font-weight: 700; } /* 雅黑有 Bold,安全 */ .emphasis { font-weight: 400; } /* 不做加粗,改用颜色区分 */font-synthesis: none的代价是,某些本该加粗的地方会变成“没反应”,视觉层级丢失。所以更实用的做法是放弃用字重做层级,改用颜色、背景、左边框、字号来做强调。中文排版里,一个深色背景块或者一条左侧色条,比强行加粗有效得多。
如果确实需要多档字重,那就必须走自定义字体路线,并且使用可变字体(variable font)。思源黑体、苹方都有可变版本,一个文件覆盖多个字重,比分别加载六个静态文件省得多。这一点在中文场景下尤其重要,因为中文字体文件本身就大。
4.2 行高与字号的换算
中文行高的默认值比英文高,这不是审美问题,是字形决定的。汉字是方块结构,字身框几乎填满 em 方,上下没有拉丁字母那样的留白;英文的小写字母只占 x-height,上方还有大量空白。所以同样line-height: 1.4,英文看起来舒展,中文就挤。
我的经验值是这样:
| 场景 | 字号 | 行高 | 说明 |
|---|---|---|---|
| 正文(桌面) | 14–16px | 1.7–1.8 | 低于 1.6 长文会累 |
| 正文(移动) | 15–17px | 1.6–1.75 | 屏幕窄,行高略收 |
| 大标题 | 24–40px | 1.2–1.35 | 字越大行高比例越小 |
| 小标题 | 16–20px | 1.4–1.5 | 与正文拉开但不脱节 |
| 表格单元格 | 13–14px | 1.5 | 紧凑但不能挤 |
| 按钮文字 | 14–16px | 1 | 单行,用 display 居中 |
行高一律用无单位数值,不要写line-height: 24px。无单位值会乘以元素自身的font-size,当字号在响应式里变化时,行高自动跟随;而固定像素会导致大屏上字号放大后行高相对变紧。
还有个细节:font-size不要用px一把梭,正文用rem更好,配合根字号调整能照顾到用户的浏览器字号设置。中文用户里把默认字号调大的比例不低,用px会让这部分人的阅读体验变得很差。
4.3 中英混排的间距处理
中文和英文、数字挤在一起时,视觉上没有呼吸感,比如“共3个文件”里的3和汉字贴得很死。理想状态是自动插入约 1/4 em 的间距,但 CSS 里没有能广泛使用的属性做这件事(text-autospace的支持度还远远不够)。
实际可用的三条路。第一条是内容层加空格,在汉字和英文之间插一个普通空格,效果最稳,但需要内容侧配合,动态内容做不到。第二条是给整段加letter-spacing: 0.02em,让所有字符均匀拉开一点,混排处自然就有了间隙,代价是汉字之间也变松了,属于折中。第三条是用 JS 在渲染时给中英文交界处包一层带margin的span,效果最精确,但会污染 DOM、影响复制粘贴,长列表里性能也不好。
我现在的做法是:正文用第二条,标题和关键文案用第一条手工控制。用了letter-spacing: 0.02em之后,中文段落在 15px 字号下阅读节奏会明显好一点,代价可以接受。但要注意,letter-spacing会作用到最后一个字,导致文本末尾多出一点空隙,做居中对齐时会有轻微偏移,需要给容器加负margin抵消,或者干脆接受这点误差。
4.4 自定义字体的加载与子集化
只要你的项目用到了非系统字体,就绕不开体积问题。一个完整的中文字体,TTF 格式动辄 5–20MB,直接丢给用户等于自杀。处理路径大致是三步。
第一步,格式转换。TTF 转 WOFF2,体积能压到原来的三到四成。这一步几乎无脑做,现在主流浏览器都支持 WOFF2。
第二步,子集化。中文常用字 3500 个左右能覆盖 99% 以上的日常内容,按这个字表切出来的 WOFF2 通常在 1–2MB,还是不算小,但已经可用。工具链上是pyftsubset或各种构建插件,配置里指定字符集就行。这里有个坑:子集化时一定要把标点、数字、英文、常用符号都带上,只按汉字表切会导致页面上的引号、破折号变成方块。我一般会额外加上 ASCII 全段、中文标点区段,以及项目里的特殊符号。
第三步,分片加载。按unicode-range把字体拆成多个文件,浏览器只下载当前页面实际用到的分片。这个策略对长文站特别有效,首页往往只用得到几百个字。
@font-face { font-family: "CustomCN"; src: local("PingFang SC"), url("/fonts/cn-subset.woff2") format("woff2"); font-display: swap; font-weight: 400; }src里写local()是个省钱技巧:如果用户机器上已经有同名或同款的系统字体,直接用本地版本,跳过网络请求。对于思源黑体这种常见字体,这一行能省下不少流量。
font-display: swap的含义是先用回退字体渲染,自定义字体到了再替换。好处是首屏不白屏,坏处是会有一次明显的字体跳变(FOUT),行高、字宽全变,体验上像页面“抖了一下”。要做得好,得用size-adjust、ascent-override、descent-override这几个描述符,把回退字体的度量调到和自定义字体接近,让跳变幅度小到几乎看不出来。这一步比较费工,但做过一次之后复用性很高。
5. 排查实录:字体不生效的九个高频原因
5.1 从现象到根因的排查路径
字体问题排查有个固定顺序,按这个顺序走能省很多时间。
先看 Computed 里的 Rendered Fonts。这是唯一可信的判据,直接告诉你最终用的是什么。如果这里显示的就是你想要的字体,那问题不在字体栈,而在字号、字重、颜色这些别的属性上。
如果显示的是别的字体,再分两支。第一支是字体名本身没匹配上——检查引号、拼写、空格、大小写(大小写不敏感,但空格敏感)。第二支是字体名匹配上了但没加载成功——自定义字体要去看 Network 面板,确认请求发出去了、状态码是 200、不是 404 也不是被 CORS 拦下。
CORS 这个问题值得展开说。字体文件跨域加载时,浏览器要求服务端返回Access-Control-Allow-Origin,否则直接拒绝使用。我遇到过 CDN 上放字体、主站和 CDN 域名不同,本地开发因为同源代理看不出问题,一上线字体全丢的情况。字体文件的 CORS 头一定要在部署清单里检查一遍。
5.2 高频问题速查表
| 现象 | 最可能的原因 | 处理方式 |
|---|---|---|
| 某个平台上字体明显不同 | 该平台没有首选字体,回退到后面的项 | 按平台补全字体栈,确认兜底族存在 |
| 整条字体栈完全没生效 | 字体名拼写错误或漏引号 | 检查含空格的名字是否加引号 |
| 自定义字体在开发环境正常、线上失效 | 跨域限制或路径错误 | 检查Access-Control-Allow-Origin与构建后路径 |
| 部分字变成方块或直接回退 | 子集化时漏掉字符 | 扩大子集字符集,补 ASCII 与中文标点 |
| 某些文字明显比周围粗、边缘发毛 | 合成加粗 | font-synthesis: none或改用真实字重 |
| 数字在表格里参差不齐 | 使用了比例数字宽度 | font-variant-numeric: tabular-nums |
| 首屏文字闪烁跳变 | 自定义字体延迟加载 | font-display配合度量覆盖,或本地优先 |
| 安卓真机字体与模拟器不一致 | 系统字体差异 | 接受差异,用字号行高做视觉对齐 |
| 代码块里中文注释间距奇怪 | monospace接管了汉字 | 在中文字体后再落monospace |
5.3 我踩过的几个坑
第一个坑是字体栈太长反被拖累。早年我习惯堆十几项,什么平台都想照顾到,后来发现列表越长,维护成本越高,而且中间某一项写错会静默失败,根本不报错。现在我的原则是:桌面端最多 8 项,移动端最多 4 项,每一档都要能说清楚“为什么它在列表里”。
第二个坑是用字重做视觉层级。早期做后台系统,觉得“标题加粗、重点加粗、按钮加粗”就够了,结果在 Windows 上用雅黑渲染出一堆假粗体,整个页面看起来脏。后来改成用颜色深浅、背景块、左侧色条来做层级,反而更清爽,也不用操心字重了。
第三个坑是宋体小字号。有次做文化类专题,正文用了宋体,字号给到 14px,自己 Mac 上看着挺雅致,用户反馈里一堆“字看不清”。原因就是 Windows 的SimSun在 14px 附近走点阵渲染,笔画又细又硬。后来把字号提到 17px、行高放到 1.9,问题才解决。这个教训是:宋体在屏幕上的舒适区比黑体窄得多,字号必须放大。
第四个坑是emoji 被中文字体接管。页面里有个状态标识用了符号,在自己机器上是彩色,到某些环境变成黑白轮廓。根因就是字体栈最后没写 emoji 专用字体,系统把符号交给了中文字体里的单色字形。补上"Apple Color Emoji", "Segoe UI Emoji"就好了。
第五个坑是以为font-weight: 500是安全的。这个值在苹方、思源上确实能命中 Medium,视觉上是“稍粗一点”,很舒服。但一旦用户环境只有 Regular 和 Bold,500 会按规范回退到 400 或 700,具体取哪个取决于浏览器的实现细节,于是在不同浏览器上呈现出两种完全不同的粗细。要做跨端一致的“中等强调”,用 500 就得接受这种不确定性,要么就老老实实用 700。
最后一个经验是关于测试的。字体这种东西,本地开发环境几乎永远看不出问题,因为你的机器上装的字体最全。真正有效的验证方式是:把系统里多余的中文字体禁用掉,或者在虚拟机里跑一份干净的系统,再或者直接用远程真机看。我现在的习惯是每次动字体栈,都要在至少两个平台的真机上各过一遍,重点看三处:标题的字重对不对、表格里的数字对不对齐、代码块里的中文注释间距有没有异常。这三处过了,基本就没大问题。
至于字体栈到底选哪一套,我的态度是别追求完美方案,追求“可解释方案”。每加一项,你都得答得上来它为什么在那儿、命中不了会退到哪儿。答不上来的项,删掉比留着安全。