
1. 先聊聊字体属性这个话题在做CSS开发的路上字体属性大概是每个人最早接触、却又最容易忽略细节的一块内容。很多同学写样式的时候直接font-family: 微软雅黑加一个font-size: 14px就完事了结果UI小姐姐打开页面一看字体粗细不对、中英文混排不协调、甚至在不同设备上显示尺寸天差地别这时候才开始回头研究字体属性背后的门道。先说清楚CSS字体属性一共涵盖五个核心维度字体系列font-family、字体大小font-size、字体粗细font-weight、文字样式font-style以及把这四者打包的复合属性font。别小看这五个属性它们直接决定了页面文字最终以什么面貌呈现给用户也决定了你的页面在不同平台、不同浏览器上是否表现稳定。这篇文章适合所有写过CSS的开发者阅读——不管你是刚入门的学生还是已经工作几年的前端工程师字体属性这五个点里面一定有值得你补全的细节。尤其是混合移动端开发、多语言站点、组件库封装这几种场景字体设置踩过的坑远超你想象。接下来我会按属性逐个拆解顺带讲清楚它们之间的协作关系以及实际项目中怎么用才不至于翻车。2. 字体系列font-family——所有字体问题的源头2.1 字体栈Font Stack的设计逻辑font-family接收一个或多个字体名称按优先级用逗号分隔形成一个字体栈。浏览器会从第一个字体开始依次查找如果系统中不存在该字体就尝试下一个直到找到可用的字体为止。如果全部都不存在就使用浏览器默认字体。很多人写font-family只写一个字体名这是个坏习惯。原因很简单你无法保证用户的设备上一定装了某个字体。比如你写死PingFang SCWindows用户根本找不到这个字体页面就回退到默认的宋体视觉效果直接崩掉。一个合格的字体栈应该这样写body { font-family: PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Arial, sans-serif; }这个字体栈的逻辑是优先使用苹方macOS/iOS系统字体如果没有则尝试冬青黑体另一款macOS字体再退到微软雅黑Windows系统字体然后是无衬线的西文字体最后用sans-serif兜底。这里有一个重点字体栈中的西文字体要放在中文字体前面。为什么因为中文字体通常也包含西文字符如果先声明中文字体那么英文和数字也会用中文字体渲染在视觉上会显得偏大、不均匀。先把西文字体排前面英文走英文的渲染方案中文走到中文字体时再按拼音顺序排列这样混排效果最好。2.2 系统字体栈与Web安全字体系统字体栈指的是利用操作系统内建字体组合出的一组高效字体声明。它的好处是零加载成本渲染速度快而且能自动适配用户的操作系统。比较经典的写法是GitHub和Apple都采用过的一种解法body { font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji; }这里的-apple-system和BlinkMacSystemFont分别对应macOS和Chrome/Edge等浏览器的系统UI字体Segoe UI是Windows 10的内置字体Roboto是Android的默认字体Noto Sans是Google为多语言覆盖设计的开源字体。这种写法兼顾了主流平台是目前大型项目里用得最多的一套字体栈。Web安全字体则是指那些几乎在所有操作系统中都预装、可以放心使用的字体比如Arial、Verdana、Georgia、Times New Roman等。对于不需要特别品牌感的页面用Web安全字体就够了。但注意sans-serif、serif、monospace这三种通用族类名不是具体字体而是字体分类浏览器会从系统的对应分类中选择一个默认字体来渲染。2.3 自定义字体与性能考量当设计稿上的字体不是系统预装字体时就需要通过font-face引入自定义字体文件。这里要提醒的是字体文件非常影响性能尤其是中文全量字体动辄几MB甚至十几MB。所以实际项目中一般只会引入西文字体的不同字重中文则使用字重模拟或者系统字体代替这是最常见的取舍。font-face { font-family: MyFont; src: url(./fonts/MyFont-Regular.woff2) format(woff2); font-weight: 400; font-display: swap; } font-face { font-family: MyFont; src: url(./fonts/MyFont-Bold.woff2) format(woff2); font-weight: 700; font-display: swap; }这段代码的关键点有三个第一src中优先使用woff2格式它是目前压缩率最高、兼容性较好的字体格式体积比woff小约30%左右。第二每个字重单独声明一个font-face这样浏览器只在需要对应字重时才去下载对应文件避免一次性加载所有字重。第三font-display: swap表示在自定义字体加载完成之前先用系统字体渲染文本字体加载完成后自动替换防止页面文字不可见FOITFlash of Invisible Text的问题。3. 字体大小font-size——千万别只会写px3.1 各种长度单位的适用场景font-size支持的单位非常多样常见的有px、em、rem、vw/vh以及百分比。它们的适用场景和计算规则完全不同用错了就会出现“为什么我的字这么大/这么小”的疑惑。px是绝对单位1px固定等于屏幕上的一个物理像素点(实际上还要看dpr这里不展开)。它的优点是直观、可控、设计稿还原度高缺点是无法随用户浏览器的默认字号设置而变化。em是相对单位相对于当前元素的font-size计算。举个例子.parent { font-size: 16px; } .child { font-size: 1.5em; /* 实际等于 16 * 1.5 24px */ }如果.child内部还有子孙元素再使用em就会继续叠加很容易出现字号越嵌套越大的情况这是em用在字体大小上的最大痛点。rem同样是相对单位但它只相对于根元素html的字号计算。默认情况下根元素字号是16px所以1rem 16px。这个特性让rem成为响应式布局中控制全局字号比例的首选单位——只需改变根元素的font-size整个页面的字号就按比例缩放。vw/vh是视口单位1vw等于视口宽度的1%。用它做字体大小可以实现“字号随浏览器窗口大小平滑变化”的效果但缺点是窗口过窄时字会太小窗口过宽时字会太大所以一般不直接用于正文而是配合clamp()函数做流式字号。3.2 实际项目中的单位选型建议我在真实项目里的经验是老项目用px新项目推荐rem或者px媒体查询的方案如果团队水平足够可以用clamp()做精细化控制。为什么推荐rem因为rem能更好地兼顾可访问性和响应式。举个例子如果用户把浏览器默认字号调大说明他有视觉上的特殊需求使用rem布局的页面会跟随根元素的字号一起放大而使用px布局的页面则纹丝不动——这对用户体验的影响是肉眼可见的。至于clamp()它是CSS提供的一个取值范围函数语法是clamp(MIN, VAL, MAX)非常适合做流式文字大小控制h1 { font-size: clamp(24px, 4vw, 48px); }上面这行的意思是标题字号最小24px最大48px在这个范围内随视口宽度动态变化。注意clamp()的中间值最好用视口单位不要用百分比百分比在font-size中指的是父元素字号的倍数而不是视口宽度。3.3 为什么有时候设置的字号不生效很多人遇到过一个诡异的现象明明给某个元素设置了font-size但页面毫无变化。我把常见的成因列在这里被更高优先级的选择器覆盖了排查一下是否有!important或者其他更具体的选择器。元素继承的是父级或根元素的字号而你的设置写在了没有匹配到该元素的类中。如果设置了font-size但没有指定行高浏览器可能会因为行高公式计算问题出现视觉是“字号没变”的错觉。详见后面的行高论。某些移动端浏览器默认会对大于16px的字号进行自动缩放比如iOS下横屏时的自动调字需要在viewport的meta标签里设置maximum-scale1或者用text-size-adjust属性修正。4. 字体粗细font-weight——不只是bold和normal4.1 字重的数值含义font-weight的常见取值有三种形式关键字normal、bold、相对关键字lighter、bolder和数值100~900以100为间隔。这里的数值对应的是字体文件中预设的字重等级。100最细Thin400是正常Regular700是加粗Bold900最粗Black。需要特别指出的是数值400等于normal700等于bold但反过来不成立。如果你给一个只有Regular和Bold两种字重文件的字体设置font-weight: 500浏览器会怎么做呢它会从可用字重中找一个最接近500的来渲染——大多数情况下它会显示为400的Regular因为浏览器倾向于向数值更小的方向匹配500会降到400。同理600会升到700。但如果你设置的是550这种非标准间隔行为就可能不稳定了所以尽量使用标准字重数值。4.2 中文字体与字重的坑这是中文Web开发最容易踩的坑之一。很多中文字体只有两个字重常规体和粗体。就算你设置了font-weight: 700如果系统里没有对应字重的字体文件浏览器只能通过算法对普通字体进行“伪加粗faux bold”——也就是把字形向四周加粗描边。伪加粗在小字号下通常看不太出来但在大字号展示、或者文字带背景色/渐变的时候会出现边缘发糊、笔画不均的问题。更糟糕的是如果设计师在UI中用了思源黑体这样的多字重字体而你的页面上没有正确加载对应的字重文件那么浏览器匹配到的字体可能是错的所有文字都会变成一个奇怪的粗细。解决方案有两种其一如果只是局部需要先确认字体文件确实包含了目标字重再设置对应的font-weight数值。其二如果字体文件缺失无法加载而你又必须呈现偏粗的效果可以用text-shadow模拟加粗.font-weight-fallback { font-weight: 400; text-shadow: 0.02em 0 0 currentColor; }text-shadow的水平偏移加上文字本身视觉上就会产生轻微的加粗感。这是个全浏览器兼容的巧办法但只建议在标题等场景临时使用渲染性能上不如真正的字体字重。4.3 font-synthesis与字重渲染控制font-synthesis是控制浏览器是否允许“合成”缺失字体样式的一个属性。它可以分别控制粗细合成和斜体合成font-synthesis: none; /* 禁止所有合成 */ font-synthesis: weight; /* 只允许合成粗细 */ font-synthesis: style; /* 只允许合成斜体 */ font-synthesis: weight style; /* 允许合成粗细和斜体 */在多语言场景下这个属性特别有用。举个例子日文字体通常没有斜体设计如果给日文文本设置font-style: italic浏览器会对日文字形做强制倾斜显得非常生硬。设置font-synthesis: none就能避免这种情况让浏览器忽略斜体和加粗的合成请求。5. 文字样式font-style——italic和oblique之间的差距5.1 正常、斜体与伪斜体font-style有三个值normal、italic、oblique。normal就是正常直立文字italic是倾斜的斜体文字oblique则是将正常的字形做倾斜变形。这里要说清楚一个概念italic和oblique并不是一回事。italic是一个独立的字体变体它不仅仅是把字母倾斜了而是整个字形都被设计成带有书法特征的形态——比如小写字母a会从单层变成双层结构f的尾巴会更弯。而oblique只是把正常的字体做机械倾斜。大多数中文字体没有专门的italic设计所以即使你设置font-style: italic中文部分往往也只是被机械倾斜。这不代表不能用而是说你要知道页面上看到的“斜体中文”实际上是对原字形的拉伸变形。5.2 中英文混排时的italic注意事项在中英文混排的场景下把一段文字整体设置为italic英文部分会呈现真正的斜体效果中文部分则呈现机械倾斜的效果。由于中文汉字的笔画结构复杂机械倾斜很容易产生视觉上的拥挤感或割裂感影响阅读体验。一个比较稳妥的处理方式是用font-style只作用于西文部分pThis is an span classenamazing/span 中文示例/p.en { font-style: italic; }或者反过来把中文单独用span包裹并保持normal英文保持在italic状态下。具体怎么做取决于你的设计需求关键是脑子里要有“中西文可能共享一套font-style但渲染表现完全不同”的意识。5.3 font-variant与小型大写字母font-variant与font-style不在一个维度上但它也是文字样式家族的一员。它最常见的值是small-caps也就是小型大写字母把小写字母渲染成大写形状但尺寸缩小。.small-caps { font-variant: small-caps; }在实际应用中small-caps一般用于英文缩写词、品牌名称等场景。不过中文字体不支持这个属性所以使用前要确认目标字符集的范围避免中英文混排时出现中文不受影响、英文全部变成小型大写的割裂感。6. 字体复合属性font——一次声明全部搞定6.1 简写的语法规则font是一个复合属性它允许你在一条声明中同时设置字体的多个属性。完整的语法顺序是font: [font-style] [font-variant] [font-weight] [font-size/line-height] [font-family];其中前三个属性font-style、font-variant、font-weight都是可选的并且顺序不限。但**font-size和font-family是必填的**缺少任何一个整个font简写都是无效的。一个典型的例子body { font: italic 700 16px/1.5 PingFang SC, Microsoft YaHei, sans-serif; }这行代码等价于body { font-style: italic; font-weight: 700; font-size: 16px; line-height: 1.5; font-family: PingFang SC, Microsoft YaHei, sans-serif; }看到了吧16px/1.5中的16px对应font-size1.5是line-height行高它是通过斜杠跟在font-size后面的这是font简写里唯一能同时设置行高的位置。6.2 简写的隐式重置陷阱使用font简写时一个最大的坑在于凡是简写中没有出现的字体相关属性都会被重置为初始值。这是什么意思呢假设你在样式中先定义了p { font-style: italic; font-weight: 300; }后来又想用一条font简写统一调整字号和字体族p { font: 14px/1.6 Arial, sans-serif; }此时font-style和font-weight并不会被保留为italic和300而是被重置为初始值normal和400。这是CSS简写属性的通病——background、border、animation都有类似的行为但font的重置范围更隐蔽因为它把跟字体相关的一整排属性都收纳了。所以在使用font简写时要么确保这一条声明是控制该元素字体的唯一入口要么把需要保留的其他属性放到font简写之后再声明。6.3 system-ui与font简写的高阶用法font简写中也可以使用系统字体关键字比如caption、icon、menu、message-box、small-caption、status-bar。这些关键字的含义是“使用操作系统对应UI组件使用的字体”。虽然在实际Web开发中很少用到但如果你在做浏览器插件或桌面应用的内嵌页面想尽量贴合宿主环境这是一个值得了解的能力。另外配合CSS变量使用font简写可以做主题化配置:root { --font-sans: PingFang SC, Microsoft YaHei, sans-serif; --font-size-base: 16px; --line-height-base: 1.75; } body { font: 400 var(--font-size-base) / var(--line-height-base) var(--font-sans); }注意CSS变量在font简写中的位置有讲究——var()可以直接替换某个子属性但你不能把整个font的值塞进一个变量后在别处展开至少在原生CSS语法下做不到需要配合工具链或者使用原生嵌套等方式。7. 实操中的高频问题与排查技巧7.1 字体相关属性的继承规则字体属性大部分是可继承的包括font-family、font-size、font-style、font-weight等。这意味着父元素设了字体子元素如果不额外指定会沿用父级设置。这是好事但也会带来一个麻烦不同元素比如input、button、textarea的用户代理默认样式会覆盖继承的字体。这是几乎所有前端新手都会遇到的问题——你给body设置了font-family但button里面的文字依然是浏览器默认的字体看起来格外突兀。解决方法是重置表单控件的字体继承button, input, select, textarea { font: inherit; }font: inherit表示这几个属性全部继承自父元素一条代码解决所有表单控件的字体不一致问题。这个写法应该是每个项目的全局样式标配我还没见过哪个正经项目不需要这行代码的。7.2 行高、字高与文字垂直居中对齐设置字体大小时很多人会顺带发现一个现象明明font-size: 16px但文字所在的行盒高度并不等于16px而是更大一些。这是因为行高line-height默认值一般由字体本身决定通常在1.2~1.5之间。在设计稿中如果要求某个容器内文字严格垂直居中很多初级开发者会直接line-height等于容器高度。但要注意当font-size很大比如超过24px时不同字体的实际内容区高度不一样仅仅设置line-height容器高度可能产生视觉上的偏移。我的处理习惯是需要严格垂直居中的单行文本除了设置line-height等于容器高度外还要注意font-family中是否包含会导致基线偏高的字符集比如中文和英文混排时的基线差。如果实在对不齐可以考虑使用flex布局.container { display: flex; align-items: center; justify-content: center; }在flex布局下align-items: center基于元素的content-box进行对齐比line-height的方式稳定得多。关于这点网上看过有人争论line-height垂直居中更好还是flex更好实际测试下来flex的兼容性和一致性都更好特别适合弹窗按钮、标签页这类结构简单的容器。7.3 中英文混排字号不协调的处理中英文混排时英文和数字即便设置相同的font-size视觉上也会显得偏大或偏小。这跟字符的em box设计有关拉丁字母的上下留白比汉字多视觉中心也不同。常见的问题是“英文数字显得比中文高”解决办法是给西文单独设置font-size通常是中文字号的0.85~0.9倍。.text { font-size: 16px; } .text .en, .text .num { font-size: 0.9em; }还有一种情况是中文的标点符号和英文单词对齐问题这需要line-height、word-spacing等多个属性配合调整。在一般内容型页面中不必过度纠结但在数据可视化面板、标题字特别大的场景下0.05em级别的调整肉眼就能分辨出好坏值得花时间精修。7.4 工具属性盘点IDE与调试面板的字号调整在不同的开发或测试工具中字体大小调整往往不归CSS管很多人在折腾的时候容易绕弯。比如有人问DBeaver字体大小怎么设置——那是数据库客户端自己的界面设置跟CSS无关。DBeaver的字体调整步骤是打开Window → Preferences → General → Appearance → Colors and Fonts然后展开Text Font点击Edit修改即可。JMeter调整界面的字体大小也是类似逻辑打开Options → Look and Feel → Font Size可以整体缩放界面字体。如果你在用VS Code写CSS想调整编辑器里的字体大小快捷键是Ctrl /Ctrl -macOS上是Cmd /Cmd -或者通过修改settings.json里的editor.fontSize。Word的批注字号不能直接改的问题本质上是Word对批注框内文本的字号做了锁定。它不是CSS的问题但和字体相关的调试思路是一致的——先确认工具是否支持、再去寻找正确的入口而不是在不支持的配置项上死磕。做前端调试时同理观察Chrome DevTools中Computed面板里字体属性的最终计算值而不是反复猜测哪个样式覆盖了自己写的规则。用getComputedStyle()直接验证一个元素最终的字体状态是定位字体问题最高效的手段。8. 一个完整案例从设计稿到高还原的字体实现8.1 需求描述与技术选型假设UI给了一个移动端页面设计稿页面标题字号48px正文16px行高1.75标题是加粗效果并且整体希望使用系统默认字体栈。还原这个设计稿可以按下面的步骤来决定字体设置方案。首先明确字体栈。移动端以iOS的苹方和Android的思源黑体或Roboto为主所以全局设置:root { --font-family-base: -apple-system, BlinkMacSystemFont, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Noto Sans SC, sans-serif; }8.2 标题与正文的字体设置页面标题用clamp()控制在不同屏幕宽度下的字号h1 { font-family: var(--font-family-base); font-size: clamp(32px, 8vw, 48px); font-weight: 700; line-height: 1.2; }正文统一使用基础字号并通过font简写集中管理body { font: 400 16px/1.75 var(--font-family-base); margin: 0; }注意这里font简写内部同时设置了font-weight、font-size、line-height和font-family四个属性逻辑上很紧凑但副作用是body下各种元素原有的字体相关设置都会被重置。于是紧接着放一条重置button, input, select, textarea { font: inherit; }8.3 从计算值到像素级的核查做完布局之后建议在开发者工具里选中标题元素和正文元素分别查看Computed面板的font-family、font-size、font-weight、line-height四个属性值确认最终计算值和设计稿一致。这里有个细节font-family在计算后显示的值可能和你写的不完全一样。比如某些浏览器会按照字体栈的匹配结果直接显示为最终命中的字体名而不是你写的完整字体栈。不要慌这是正常的。真正需要警惕的是计算值显示为serif或Times New Roman——这说明字体栈里的所有字体都没命中回退到了默认字体这时要回头检查字体名拼写和字体加载路径。8.4 移动端视口与字号的适配移动端1px的CSS像素和物理像素的关系在普通分辨率下是1:1在2倍屏dpr2下1个CSS像素对应4个物理像素。所以设计稿上48px的字在2倍屏上用font-size: 48px就能还原物理尺寸不需要把字号翻倍。这一点很多人误解以为“高清屏要把字也变大”其实字体大小是CSS单位跟dpr没有直接换算关系。真正需要关心的是viewport的配置。确保HTML中设置了meta nameviewport contentwidthdevice-width, initial-scale1.0这样可以避免移动端浏览器将页面当作980px的桌面页面缩放导致所有字号看起来都非常小。没有这个meta标签你做的所有字号控制都会在移动端变得不可预测。9. 避坑清单与经验总结整理一下我实操中积累的关键要点做成一个速查清单方便你写代码时对照。font简写时必须同时提供font-size和font-family否则整条声明失效。使用font简写会重置所有未声明的字体属性不要在已有的字体规则后面随意追加简写。中文字体名字如果包含空格需要加引号比如Microsoft YaHei英文字体名不加引号通常也能用但建议统一加引号避免意外。font-size使用em时会逐层累加嵌套层级深的情况下容易失控优先使用rem或px。设置font-weight前先确认目标字体的字重文件是否已加载否则会触发伪加粗导致渲染发糊。中文斜体大多为机械倾斜没有明显的字形变化。如无必要不要给大段中文设置font-style: italic。表单控件的字体不会自动继承全局字体用font: inherit强制继承。不同浏览器的默认字体可能不同全局样式表第一行建议显式声明font-family和font-size而不是依赖默认值。最后说一下我最常被问的问题——“明明设置了字体为什么显示不对”。十有八九是这几个原因字体加载失败路径错误或跨域、字体栈没有兜底字体、样式被更高优先级覆盖、或者font简写顺带重置了其它属性。排查的时候按这个顺序走基本一轮就能定位问题。字体属性不像transform、animation那样花哨但它是网页信息传递的基础载体。把字体系列、字体大小、字体粗细、文字样式和复合属性这五个点吃透等于掌握了让页面文字准确、舒适、稳定地呈现的核心能力。希望这篇文章能帮你省去一些自己摸索的时间让你在写字体相关样式时少一些“为什么不对”的困惑多一些“就该这样写”的笃定。