CSS字体属性大概是前端入门时最早接触、却最容易稀里糊涂带过去的一块。很多人写font-family就手打一个 Arial,写font-size就随便给个 14px,等到做真实项目才发现:字体栈顺序不对、中文乱码、font-weight: bold在部分浏览器里根本没加粗效果、复合属性一写就把别的样式全重置了。今天把字体属性整个体系拆开讲一遍,字体系列、字体大小、字体粗细、文字样式、复合属性,每一条都用真实项目里验证过的写法来讲,适合刚入门 CSS 的前端学习者,也适合基础不牢、想回来补课的同学。这篇不会绕弯子,全是实际能用的东西。
1. 为什么字体属性值得单独拿出来写一篇
1.1 字体属性看着简单,坑比想象的多
字体相关属性在 CSS 里属于高频中的高频,几乎每个页面都会用到。但正因为太常用了,大部分人都是“能用就行”的心态,从来没系统整理过。等真遇到问题的时候,往往卡在最基础的地方。我梳理了几个最常见的现象:
- 设置了
font-family: "微软雅黑",但用户电脑里没有这个字体,页面直接回退成默认宋体,排版瞬间变丑。 font-size用了em,结果嵌套层级一深,字号越变越大,怎么调都不对。font-weight: bold写在样式中,浏览器却显示成普通粗细,乍一看像是没生效。- 用
font简写属性同时设置多个值,写完字号后间距、行高全乱了。
这些问题每一个我都实际踩过。它们不是多高深的技术难点,而是对字体属性背后的机制理解不够。与其靠百度查一条改一条,不如把整个体系过一次。
1.2 这篇内容适合谁,能解决什么问题
如果你是刚学 CSS 的入门者,这篇可以帮你把字体相关的知识点一次串起来,省去翻各种零散教程的时间;如果你已经写过不少页面但基础不牢,这篇能帮你补上“原理”那一层,以后遇到问题能自己判断原因,而不是改一个数试一次。
我还会额外讲一些需要实际项目经验才能总结出来的细节,比如字体栈的排列策略、常见中文 Web 字体的兼容差异、rem和em的计算技巧、复合属性最容易忽略的规则等。这些内容在官方文档里都有,但很少有人告诉你哪个坑在真实项目里出现频率最高。
2. 字体系列(font-family)从入门到会踩坑
2.1 字体栈的顺序,就是你的备选方案列表
font-family接收一个优先列表:浏览器会按照从左到右的顺序寻找可用字体,如果第一个字体不存在,就依次使用后面的字体,直到找到系统里有的那张脸。
body { font-family: "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif; }这段代码的意思很直白:在苹果设备上优先用苹方,Windows 上优先用微软雅黑,都没有的时候退回 Helvetica Neue,再不行就用 Arial,终极兜底是无衬线字体族。
实际写的时候,我会把字体栈分成三段来看:
- 第一段是“期望字体”,通常是针对目标用户系统做的最优选择。
- 第二段是“中性替代”,保证主流系统都有相似的观感。
- 第三段是“通用兜底”,也就是
sans-serif、serif或monospace,确保浏览器不会乱选字体。
这种做法的好处是:不需要给每个用户写一套独立样式,浏览器自己就能完成降级流程。关键是顺序不能乱,否则期望字体会被后面的字体覆盖,等于白写。
2.2 字体名加引号的规则,很多文档没讲透
中文字体、包含空格的英文字体名必须加引号,比如"Microsoft YaHei"、"PingFang SC"。不加引号的大部分情况下浏览器也能识别,但遇到特殊字符就会有解析错误的风险。当字体名恰好是 CSS 关键字的时候,引号更是必须的,例如font-family: "default"。
我用项目里实际遇到的情况举个例子:有人写了font-family: PingFang SC, Microsoft YaHei, sans-serif;,不加引号。在大多数现代浏览器里能正常显示,但在一些低版本内核或移动端 WebView 里,空格会导致解析中断,字体直接回退。为了兼容性,我习惯统一加上引号,养成固定写法。
还需要注意一点:font-family的值可以写字体族名称,也支持多个字体名组合成一个带引号的整体字体族。如果你在字体栈里用到了“锦华细黑”,完整写法是:
font-family: "方正兰亭细黑_GBK", "PingFang SC", "Microsoft YaHei", sans-serif;2.3 中文字体的“现实问题”与系统差异
中文字体和英文字体最大的不同在于,中文字体体积大、字符集全,同一个系统只有有限的几种内置中文字体。Windows 一般是微软雅黑和新宋体,macOS 是苹方和宋体,Linux 服务器上甚至可能只有文泉驿或思源黑体。
这就导致一个结果:你没法保证所有用户看到的字体都一样。所以中文字体栈的优先级要结合业务受众来定。如果面向国内 PC 用户,微软雅黑放在最前面没问题;如果面向苹果用户,苹方优先级更高;如果做移动端 H5,iOS 和 Android 各自主流字体不一样,字体栈往往要写得更细。
还有一个常被忽略的细节:系统没有指定字体时,浏览器对中文渲染会使用默认字体,这个默认字体在不同系统、不同浏览器里表现差异很大。为了页面整体风格可控,sans-serif这个兜底不能漏,尤其是中文页面。
3. 字体大小(font-size)的绝对与相对逻辑
3.1 绝对单位 vs 相对单位,该怎么选
font-size常用的单位分两类:绝对单位和相对单位。绝对单位有px、pt、in、cm、mm等,浏览器适配中最常用的是px。相对单位有em、rem、%、vw、vh等,它们会基于某个基准值动态计算。
在普通项目里,px最直观,设计稿给多少就写多少。但px是固定像素值,用户调整浏览器字号时不会缩放,这对可访问性不太友好。相对单位则更适合需要响应式缩放、或者需要跟随用户偏好设置的场景。
实际开发中我推荐一个组合思路:
- 全局基准字号用
rem或px定义。 - 组件内部局部间距、图标尺寸、字号缩放,可以用
em。 - 需要随视口宽度变化的场景,用
vw或clamp()。
这样既有绝对单位的精确,又有相对单位的灵活,不会陷入“全站用 em 导致嵌套倍数失控”的局面。
3.2 理解 em 和 rem 的基准:根元素与父元素
很多初学者分不清em和rem,核心区别就在“基准”。
em是相对父元素字号计算的。比如父元素字号是 20px,子元素font-size: 1.5em就是 30px。嵌套层级越多,按乘法累计,深层元素很容易变得无法预估。
rem只相对根元素<html>计算,默认为 16px。所以1rem = 16px,2rem = 32px,不受中间嵌套影响。
来看一个实际例子,假设 HTML 结构如下:
<div class="outer"> <p class="inner">内层文字</p> </div>CSS 设置:
html { font-size: 16px; } .outer { font-size: 20px; } .inner { font-size: 1.5em; /* 相对父元素 20px,实际为 30px */ }如果把.inner的font-size改成1.5rem,实际字号就是1.5 * 16px = 24px,不会受.outer影响。
我在项目里更倾向用 rem 做全局字号控制,因为只需要调整根元素一行代码,就能让全站比例跟着变。这个技巧在做主题换肤的时候极其好用。
3.3 移动端适配中字体大小怎么定
移动端做字体大小适配,最原始的办法是手写媒体查询,每个断点单独设置html { font-size: ... },然后全站用 rem。这种方法维护成本高,而且断点之外的宽度没有覆盖,字体会显得不协调。
更好的方式是用vw配合函数动态计算基准字号。比如:
html { font-size: calc(16px + 4 * ((100vw - 320px) / 680)); }这段代码的意思是:视口宽度从 320px 变化到 1000px 时,根字号从 16px 平滑变化到 20px。超出范围后不再继续缩放,避免极端大屏上字体过大。
如果你不想手动算,也可以直接使用clamp()函数:
html { font-size: clamp(16px, 1rem + 0.5vw, 22px); }这样写之后,正文和其他模块只要使用 rem,就能跟着根字号联动。整个页面字体比例保持一致,又不会在小屏过大或大屏过小。需要注意的点是:不要让根字号在大屏上无限变大,否则页面会被撑得很奇怪。
4. 字体粗细与文字样式
4.1 font-weight 不只是 bold 和 normal
font-weight常见的关键字有normal、bold、bolder、lighter,也支持数值100到900。平时写font-weight: bold和font-weight: 700在语义上等价的,但数值能提供更精确的控制。
关键问题是:字体本身必须包含对应的字重,否则浏览器会进行“伪造加粗”或“合成加粗”。如果使用系统默认字体,细体、粗体还比较全;如果从 Web 字体加载字体,但没有引入对应的字重文件,font-weight: 300可能直接没有效果,或者浏览器用默认字重替代。
一个典型例子:
@font-face { font-family: 'MyFont'; src: url('myfont-regular.woff2') format('woff2'); font-weight: 400; }这时页面里写font-weight: bold,浏览器找不到对应的 700 字重文件,就会用 400 的字形做“合成加粗”,加粗效果可能发虚、变形。正确做法是给@font-face添加多个字重来源:
@font-face { font-family: 'MyFont'; src: url('myfont-regular.woff2') format('woff2'); font-weight: 400; } @font-face { font-family: 'MyFont'; src: url('myfont-bold.woff2') format('woff2'); font-weight: 700; }这样设置后,浏览器才能根据页面里的font-weight数值切到对应的字体文件。
如果你只是用系统字体,font-weight: 300在日常页面里也未必有效,因为 Windows 的微软雅黑常规和粗体之外没有更多字重层级。这种情况下你写 300、400、500 看起来可能完全一样,不要以为是代码错了,是字体源本身不支持。
4.2 font-style 和 font-variant 的用途边界
font-style的典型值是normal、italic、oblique。italic使用字体文件中的斜体字形,oblique是浏览器对普通字形做倾斜变形。大多数英文字体自带 italic 字形,中文字体基本没有斜体概念,所以font-style: italic对中文来说,浏览器通常也会做变形处理。
font-variant现在更多用来控制小型大写字母之类的高级排版特性,日常页面用得少。但需要知道它会在某些字体设置中影响文本显示效果,如果你在样式里突然发现文本变了形状,可以检查是不是有全局的font-variant设定。
4.3 中文环境下字体加粗的显示差异
中文字体的加粗需要注意:微软雅黑和苹方等系统字体在显示加粗时,浏览器生成的“合成粗体”往往只是把字形变宽,实际视觉上并没有真正的粗体字形那么好。遇到要求较高的设计稿,我一般不会直接font-weight: 700,而是选择语义清晰的标题层级,或者改用字重更清晰的字体文件。
例如思源黑体有 100 到 900 多个字重档位,配合 Web 字体加载后,粗细变化是平滑的。如果项目要求严格按设计稿还原,可以引入完整字重的中文字体文件,再通过font-weight精确控制。代价是字体文件体积增大,需要权衡加载速度。
5. 字体复合属性 font:一行代码的威力与陷阱
5.1 复合属性的语法顺序
font是一个简写属性,可以一次性设置字体样式、字重、字号、行高和字体系列。它的语法有固定顺序:
font: font-style font-variant font-weight font-size/line-height font-family;前三个值可省略,省略时使用默认值。font-size和font-family是不可省略的,否则整个声明无效。line-height必须跟在font-size后面用斜杠分隔。
一个完整例子:
p { font: italic 700 16px/1.8 "PingFang SC", "Microsoft YaHei", sans-serif; }这段代码等价于:
p { font-style: italic; font-variant: normal; font-weight: 700; font-size: 16px; line-height: 1.8; font-family: "PingFang SC", "Microsoft YaHei", sans-serif; }使用简写能让代码更精简,但也容易踩坑:任何被省略的属性都会重置为初始值。比如你只想设置字号和字体,结果其他样式被清掉了。
5.2 复合属性“重置”问题与正确用法
我在项目中见过一个很典型的 bug:某个按钮样式里写了font: 14px/1.5 sans-serif;,结果发现按钮上的字变成了斜体。排查半天,最后发现是父级有font-style: italic,而font简写把font-style重置为了normal,又把继承的斜体设置覆盖了。这其实不是 bug,而是复合属性的机制——凡是没有写出来的项,都会恢复成初始值。
所以我的建议是:
- 只在明确要同时设置多个字体属性的场景用
font简写。 - 如果只需要调某一个值,比如单独改字号或字重,就直接写单一属性,不要用简写。
- 在重置样式的场景里,可以用
font: inherit让子元素完全继承父元素的字体设置,这比逐个声明更省事。
另外一个容易被忽略的点是line-height写在font简写里时,如果省略,line-height会被重置为normal。如果你先前设置了line-height: 1.6,后来加了一行font: 16px sans-serif;,行高就会被冲掉,影响很隐蔽。
6. 常见问题与排查技巧
6.1 font-family 明明设置了却不生效
这种情况最常见发生在字体名写错或字体不存在时。比如用户电脑里没有“方正兰亭黑”,浏览器会直接跳到后面可用的字体。还有可能是字体名拼接有问题,比如"Microsoft YaHei"写成了"MicrosoftYaHei",或者"PingFang SC"写成了"PingFangSC"。
排查时可以用浏览器的开发者工具,选中目标元素,查看“计算样式”面板中的font-family,浏览器会自动显示实际使用的字体。如果显示的还是默认字体,就说明前面写的所有字体都不可用。
此时需要检查服务器是否通过@font-face提供了 Web 字体,或者页面是否引入了外链字体库。如果是本地字体,还要看用户操作系统是否具备该字样。
6.2 font-weight 设置 700 没变化
这里有个很容易忽略的点:如果字体文件本身只有一个字重,font-weight无论写多少,浏览器都只能拿唯一字重渲染。浏览器虽然可以做“合成加粗”,但有时视觉变化微乎其微。尤其是中文黑体,本来就偏粗,合成粗体的差异几乎不可见。
另外,font-weight的值bolder和lighter是相对继承值计算的,在多层嵌套中可能和预期不同。如果嵌套很深,建议直接用数值,不要用相对关键字。
排查方法:用开发者工具查看元素是否真的被渲染为加粗。同时确认font-family指定的字体确实存在对应的粗体文件。如果依赖 Web 字体,检查@font-face是否包含font-weight: 700的声明。
6.3 font 简写导致其他属性被重置
很多人在项目里写了类似这样的代码:
.article { font-size: 18px; line-height: 2; } .article .highlight { font: 16px sans-serif; }结果是.highlight的行高被重置成了normal。原因就是font简写没写line-height,浏览器把它恢复为初始状态。这个问题我们在 5.2 里已经分析过,应对的关键是不混用简写和独立属性。
如果项目中沿用大量旧代码,一时改不过来,我建议在公共样式里定好规则:要么全用简写,要么全用独立属性,不要在同一类属性上混搭。
6.4 字体相关调试技巧
调试字体问题时,我会在开发者工具的“Elements”面板里查当前元素的计算样式,然后手动改font-family试不同字体名,看哪个系统里有、哪个没有。还有一个土办法:临时给元素加一个浅色背景,如果文本高度明显变化,说明行高或字体的度量差异在影响布局,而不只是字体名的问题。
遇到字体加载失败的场景,可以打开控制台看是否有字体文件请求报错,比如 404 或者跨域错误。这部分在移动端 H5 里尤其常见,外链字体域名没加 CORS 头,字体就加载不了。
7. 一些实战经验与建议
我在实际项目里会把字体相关设置收敛到一个公共样式中,避免每个页面各写一套,导致风格不一致。比如在:root里定义字体变量:
:root { --font-sans: "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif; --font-serif: Georgia, "Times New Roman", "Songti SC", serif; --font-mono: "JetBrains Mono", "Fira Code", Consolas, monospace; }使用时直接引用:
body { font-family: var(--font-sans); }这样想换字体方案时,只需要改一处定义,比全站手动搜索替换舒服很多。我最近在一个后台项目里就是用这种变量方式管理三套字体的。
另外,font-size的基准值建议在根元素设定明确,给个16px起步,不要依赖浏览器默认值。中文页面里 14px 正文在部分设备上会偏小,建议正文至少 15px 或 16px,小字号只留给辅助信息。
最后一个建议:越小的项目越容易忽视字体加载性能。@font-face引入的字体文件尽量用woff2格式,必要时做子集化,只保留用到的字符集,体积能减少非常多。不要为了一个标题字体外链一个几 MB 的字体文件,整体体验会明显变差。调试多了你就会发现,字体属性没有多么玄乎,只要理解了字体栈的降级逻辑、相对单位的计算基准、复合属性的重置规则,很多问题一眼就能看出来。