4. 字体与文本渲染:字体回退机制、中文字体兼容性、行高与基线对齐问题、富文本解析差异
字体移植,看着简单,其实坑最多。
我见过太多团队,UI 稿上漂漂亮亮,一跑到目标设备上,文字要么挤成一团,要么上下错位,要么直接变成方框。说白了,字体渲染这件事,每个平台都有自己的小脾气。
这一章,我把这些年踩过的坑,一个一个说清楚。
4.1 字体回退机制:别让用户看到「豆腐块」
什么叫「豆腐块」?就是本该显示文字的地方,出现了一个个方框或者问号。用户看到这个,第一反应就是「这 App 坏了」。
为什么会这样?因为目标设备上没有你指定的字体。
核心原则:永远不要只写一个 font-family。你要给系统留足「回退」的余地。
我个人习惯的写法是这样的:
/* 推荐的回退策略 */
.font-safe {
font-family:
"PingFang SC", /* iOS 中文首选 */
"Microsoft YaHei", /* Windows 中文首选 */
"Noto Sans CJK SC", /* 跨平台开源方案 */
"Source Han Sans SC",/* 思源黑体,备选 */
sans-serif; /* 最后兜底 */
}
.font-serif {
font-family:
"Noto Serif CJK SC",
"Source Han Serif SC",
"SimSun",
serif;
}
这里有个细节要注意:字体名称的引号不能省。尤其是中文字体名中间有空格时,不加引号会被浏览器解析错误。
小技巧:我在项目中习惯把字体回退链写成「平台首选 → 跨平台开源 → 系统兜底」三层结构。这样不管用户用什么设备,至少能保证「能看」。
4.2 中文字体兼容性:你以为的「一样」其实不一样
中文字体移植,最头疼的是什么?
是同一个字体名,在不同平台上的渲染效果完全不同。
举个例子:"PingFang SC" 在 macOS/iOS 上表现完美,但到了 Windows 上,系统会直接跳过它,去找下一个回退字体。如果你没写回退,那就等着出「豆腐块」吧。
我整理了一份常用中文字体的跨平台兼容表:
| 字体名称 | macOS / iOS | Windows | Android | Linux |
|---|---|---|---|---|
| PingFang SC | ✅ 原生 | ❌ 无 | ❌ 无 | ❌ 无 |
| Microsoft YaHei | ❌ 无 | ✅ 原生 | ❌ 无 | ❌ 无 |
| Noto Sans CJK SC | ✅ 可安装 | ✅ 可安装 | ✅ 部分预装 | ✅ 常见 |
| Source Han Sans SC | ✅ 可安装 | ✅ 可安装 | ⚠️ 需配置 | ✅ 常见 |
| SimSun (宋体) | ⚠️ 兼容模式 | ✅ 原生 | ❌ 无 | ❌ 无 |
避坑指南:我曾经在做一个跨平台 SDK 时,直接用了 "PingFang SC" 作为唯一字体。结果 Windows 测试机上所有中文都变成了默认宋体,行高全乱。从那以后,我每条 font-family 都至少写三个回退。
4.3 行高与基线对齐问题:为什么文字总是「上下不居中」?
这个问题,UI 设计师最敏感。
设计稿上文字完美居中,到了代码里,要么偏上,要么偏下。你调了半天 line-height,还是不对。
嗯,这里要注意:不同平台对「基线」的定义不一样。
我画了一张图,帮你理解这个差异:
你看,同样的字号、同样的行高值,在不同平台上的实际渲染高度是不一样的。这就是为什么你调好了 iOS,Android 又歪了。
我的解决方案是:
- 不要用固定的 line-height 值。用相对值,比如
line-height: 1.5,让平台自己算。 - 针对每个平台微调。我习惯写一个 mixin 或者工具函数,按平台做 ±1px 的补偿。
- 用 flex 布局辅助居中。对于单行文字,用
display: flex; align-items: center;比纯 line-height 更可控。
实战代码:
/* 跨平台行高补偿方案 */
.text-block {
font-size: 16px;
line-height: 1.6; /* 相对值,平台自适应 */
/* iOS 补偿 */
@supports (-webkit-touch-callout: none) {
padding-top: 1px;
}
/* Android 补偿 */
@supports (-webkit-appearance: none) and (not (-webkit-touch-callout: none)) {
padding-bottom: 1px;
}
}
4.4 富文本解析差异:同一个 HTML,不同的「长相」
富文本移植,是字体渲染里最隐蔽的坑。
你想想看,同样的 <strong> 标签,iOS 的 UILabel 解析出来是系统粗体,Android 的 TextView 可能直接忽略,WebView 又有一套自己的渲染规则。
我遇到过最典型的问题:
- 加粗效果不一致:有些平台对
font-weight: 600和700的渲染完全一样,有些则区分明显。 - 斜体支持缺失:中文字体很多没有斜体字重,平台会强行「伪斜体」,效果惨不忍睹。
- 列表缩进差异:
<ul>和<ol>的默认 padding 各平台不同,导致排版错位。
我建议的做法是:
- 富文本内容尽量用最基础的标签:
<b>、<i>、<u>比<strong>、<em>更稳定。 - 不要依赖默认样式:每个平台都显式重置一遍 margin、padding、font-weight。
- 用属性替代标签:比如用
color属性代替<font>标签。
避坑指南:我曾经在移植一个新闻阅读器时,后台返回的富文本里用了 <span style="font-weight: bold;">。iOS 原生渲染没问题,但 Android 的 TextView 直接忽略了行内样式。最后我不得不在客户端做了一层「样式转属性」的预处理。
4.5 本章小结
字体与文本渲染,说白了就是「别替用户做决定」。你不知道用户设备上装了什么字体,也不知道平台会怎么渲染你的文本。
你唯一能做的,就是:
- 写好回退链,保证「有字可显」
- 做好平台适配,保证「位置不偏」
- 简化富文本,保证「样式不乱」
做到这三点,字体移植的坑,你至少能避开八成。