第4章 CSS字体属性实战:从堆栈到OpenType特性
字体调优这件事,说简单也简单,说复杂也复杂。很多同学觉得「不就是设个 font-family 嘛」,结果上线后 Chrome 和 Safari 显示效果天差地别。嗯,今天我们就来把这几个核心属性彻底讲透。
本章核心脉络:font-family 堆栈设计 → font-weight 精确控制 → font-size 与行高配合 → font-variant 与 OpenType 特性。四步走,让你的字体在任何设备上都「稳如老狗」。
4.1 font-family 堆栈设计:别让用户看到「默认宋体」
font-family 堆栈,说白了就是给浏览器准备一个「备胎列表」。你最喜欢的字体排第一,如果用户电脑上没有,就往后顺延。我见过太多项目只写了一个字体名,结果 Windows 用户直接看到宋体,整个设计感瞬间崩塌。
我的经验:堆栈至少写 3 层。第一层用你想要的字体,第二层用同类系统字体,第三层用通用字体族(serif/sans-serif)兜底。
来看一个实际案例。假设我们要用 PingFang SC 做中文字体,但 macOS 和 iOS 才有,Windows 上没有。这时候堆栈应该这样写:
/* 推荐的中文字体堆栈 */
body {
font-family:
"PingFang SC", /* macOS/iOS 首选 */
"Microsoft YaHei", /* Windows 首选 */
"Hiragino Sans GB", /* 旧版 macOS 备选 */
"Noto Sans CJK SC", /* Linux 备选 */
sans-serif; /* 终极兜底 */
}
/* 英文字体堆栈(搭配使用) */
body {
font-family:
"Inter", /* 现代无衬线 */
"Segoe UI", /* Windows 系统 */
"Helvetica Neue", /* macOS 系统 */
Arial, /* 通用备选 */
sans-serif;
}
你想想看,为什么要把 sans-serif 放在最后?因为它是浏览器能识别的「通用字体族」,永远不会失效。我建议中英文混排时,把英文字体堆栈放在前面,中文字体堆栈放在后面,用逗号分隔。
注意:不要写太多字体名,3-5 个就够了。写太多反而增加浏览器匹配开销,而且用户电脑上不可能装那么多字体。
4.2 font-weight 精确控制:别被「bold」骗了
很多同学以为 font-weight: bold 就是 700,其实没那么简单。不同字体对字重的映射规则完全不同。我记得有一次项目里用了 Google Fonts 的 Noto Sans,设了 font-weight: 600,结果它直接降级到 400,因为 Noto Sans 只有 400 和 700 两个字重。
来看这张字重映射表,我建议你收藏:
| 数值 | 关键字 | 常见字体表现 | 我的建议 |
|---|---|---|---|
| 100 | thin | 极细,仅部分字体支持 | 慎用,屏幕显示容易看不清 |
| 300 | light | 细体,适合大标题 | 搭配大字号使用效果不错 |
| 400 | normal | 常规体,最稳定 | 正文首选,兼容性最好 |
| 500 | medium | 中等,介于 normal 和 bold 之间 | 适合强调性正文 |
| 600 | semibold | 半粗,很多字体不支持 | 先确认字体是否包含该字重 |
| 700 | bold | 粗体,几乎所有字体都有 | 标题和强调用,别滥用 |
| 900 | black | 特粗,仅部分字体支持 | 海报级标题可用 |
为什么会这样?因为 font-weight 的数值只是「建议值」,最终显示效果取决于字体文件本身是否包含该字重。如果字体只有 400 和 700,你设 500 或 600,浏览器会自动做「伪粗体」——说白了就是算法加粗,效果很丑。
避坑指南:我曾经在一个电商项目中用了 font-weight: 600,结果在 Firefox 上所有按钮文字都糊成一团。排查了半天才发现是字体没有 semibold 字重,浏览器做了伪粗体。从那以后,我每次用非标准字重都会先检查字体文件。
正确的做法是:用 @font-face 加载字体时,明确指定每个字重的 src:
@font-face {
font-family: 'MyFont';
src: url('myfont-regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'MyFont';
src: url('myfont-bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'MyFont';
src: url('myfont-semibold.woff2') format('woff2');
font-weight: 600;
font-style: normal;
}
/* 使用时直接写数值,浏览器会匹配正确的字体文件 */
h1 { font-family: 'MyFont', sans-serif; font-weight: 700; }
h2 { font-family: 'MyFont', sans-serif; font-weight: 600; }
p { font-family: 'MyFont', sans-serif; font-weight: 400; }
4.3 font-size 与 line-height 配合:垂直韵律的秘密
字体大小和行高,这两个属性是「连体婴」。单独调 font-size 不考虑 line-height,排版就会忽松忽紧。我个人习惯用「无单位行高」,也就是 line-height: 1.6 这种写法,它相对于当前 font-size 计算,省心很多。
来看一个实际对比:
| 场景 | font-size | line-height | 说明 |
|---|---|---|---|
| 正文 | 16px | 1.6(约 25.6px) | 阅读舒适,行距适中 |
| 大标题 | 32px | 1.2(约 38.4px) | 紧凑,节省垂直空间 |
| 小标题 | 20px | 1.4(约 28px) | 介于正文和标题之间 |
| 引用/注释 | 14px | 1.8(约 25.2px) | 行距稍大,便于阅读 |
你想想看,为什么正文行高要 1.6 而标题只要 1.2?因为正文需要连续阅读,行距太密眼睛容易串行;标题是扫读的,行距太宽反而显得松散。嗯,这里要注意:行高不要用 px 写死,否则用户缩放字体时行高不会跟着变。
我的小技巧:用 CSS 自定义属性统一管理行高,方便全局调优:
:root {
--line-height-tight: 1.2;
--line-height-normal: 1.6;
--line-height-loose: 1.8;
}
body {
font-size: 16px;
line-height: var(--line-height-normal);
}
h1 {
font-size: 2rem;
line-height: var(--line-height-tight);
}
还有一个容易被忽略的点:中英文混排时,行高要适当调大。因为中文的方块字和英文的 x-height 差异很大,同样的 line-height,中文看起来比英文挤。我一般会在中英文混排的段落里把 line-height 加到 1.7 或 1.8。
4.4 font-variant 与 OpenType 特性:专业排版的「隐藏技能」
font-variant 这个属性,说实话很多前端同学都没用过。但它能调用的 OpenType 特性,是专业排版和普通排版的分水岭。OpenType 字体里内置了很多「特性开关」,比如连字、数字样式、分数、花体等等。
来看几个最常用的场景:
/* 1. 启用标准连字(fi, fl 等) */
body {
font-variant-ligatures: common-ligatures;
}
/* 2. 使用等宽数字(表格对齐神器) */
.price {
font-variant-numeric: tabular-nums;
}
/* 3. 启用旧式数字(有上下延伸,更古典) */
.old-style {
font-variant-numeric: oldstyle-nums;
}
/* 4. 分数显示 */
.recipe {
font-variant-numeric: diagonal-fractions;
}
/* 5. 小型大写字母(标题用) */
.caps {
font-variant-caps: small-caps;
}
为什么会用到等宽数字?我举个例子。你在做价格列表时,如果数字不是等宽的,「1」和「8」占的宽度不一样,整列数字就会左右晃动,非常难看。用 tabular-nums 后,所有数字宽度一致,表格瞬间整齐了。
避坑指南:我曾经在一个金融项目中,所有金额数字都没开等宽特性。结果 QA 测试时发现,¥1,234 和 ¥9,876 在表格里对不齐,小数点后的数字歪歪扭扭。后来加了一行 font-variant-numeric: tabular-nums,问题秒解。
OpenType 特性还有一个「杀手级应用」:连字。比如英文里的 fi、fl、ffi 等组合,默认显示时字母 i 的点会和 f 的勾重叠,非常丑。启用连字后,字体会自动用连字字形替换,看起来就像手写一样流畅。
/* 完整启用 OpenType 特性 */
.text {
font-variant: common-ligatures
tabular-nums
lining-nums;
}
/* 或者用简写属性(兼容性稍差) */
.text {
font-feature-settings: "liga" 1, "tnum" 1, "lnum" 1;
}
注意:font-feature-settings 是底层属性,兼容性更好,但写法比较啰嗦。font-variant 是高级封装,更语义化。我建议优先用 font-variant,遇到兼容性问题再降级到 font-feature-settings。
最后分享一个我常用的「字体调优检查清单」:
- font-family:堆栈是否覆盖了主流系统?有没有写通用字体族兜底?
- font-weight:使用的字重是否在字体文件中真实存在?有没有伪粗体风险?
- font-size + line-height:行高是否用无单位值?中英文混排是否调大了行高?
- font-variant:数字是否需要等宽?连字是否启用?有没有用到 OpenType 特性?
这四个属性调好了,你的字体显示效果至少能提升 80%。剩下的 20% 是细节打磨,比如字间距、段落间距、首行缩进等,我们后面的章节会继续聊。