字体这块几乎所有做前端的人都绕不过去。我见过太多页面布局、配色都做得不错结果一个font-family没写对在Windows上显示宋体在macOS上显示苹方观感瞬间垮掉。今天就把font-family从基础语法到跨平台适配策略一次讲透把我在实际项目中踩过的坑和沉淀下来的方案都放出来希望能帮你少走弯路。1. 先把font-family怎么用讲清楚1.1 font-family到底是干嘛的font-family是CSS里用来定义元素使用哪种字体的属性。浏览器拿到这个属性后会按照你写在前面的优先顺序在用户的系统里去查找并渲染对应的字体。一个最基本写法长这样body { font-family: PingFang SC, Microsoft YaHei, Helvetica Neue, Arial, sans-serif; }这行代码看起来简单里面的门道可不少。你写的是整整一串字体名称浏览器会从头到尾逐个检查先看你系统里有没有PingFang SC有就用它没有就看下一个继续检查Microsoft YaHei以此类推。如果这一串字体全都找不着它才会退回使用浏览器默认字体。这种“一串字体依次查找”的机制就是字体栈font stack的设计初衷让同一个页面在不同操作系统上都能尽量渲染出设计时想要的效果。1.2 一堆字体名堆在那里浏览器到底怎么选你要理解font-family不是说你写了几个字体页面就会同时用几个字体。它永远只会挑一个来渲染。具体挑哪一个按下面这个流程判断检查用户操作系统里是否安装了列表中的字体。从左边第一个开始查找到第一个存在且可用的字体就用它。如果这个字体不包含某些字符比如英文字体里没有中文字形则浏览器不会直接跳过这个字体而是用这个字体内部定义的fallback机制来找字或者继续找列表中下一个能覆盖该字符的字体。这里有个容易踩坑的点。很多人以为写了Arial, Microsoft YaHei之后英文用Arial中文用微软雅黑。实际上Arial里面虽然没有中文字形但浏览器在处理时会把中文部分按它的fallback逻辑交给其他字体去补具体结果在不同浏览器下并不完全一样。所以如果你想明确控制英文和中文分别用什么字体最稳妥的做法是分别给英文和中文设置字体或使用font-family栈并确保中文字体放在英文字体后面且不会因缺少中文字形导致中间某名称空挂。1.3 什么时候该加引号什么时候可以不加一个超级常见的问题字体名到底加不加引号判断标准很简单看字体名里有没有空格或特殊符号。Arial、Verdana、Tahoma这种没有空格的可以加也可以不加。Microsoft YaHei、PingFang SC、Helvetica Neue这种带空格的必须加引号。中文字体名例如宋体、微软雅黑建议加引号防止在某些编码环境下解析出问题。自定义字体名例如MyCustomFont没有空格时可加可不加但是为了统一风格和安全建议都加上。在CSS里引号用单引号双引号都行但要保持前后一致不要一边单一边双。1.4 通用字体族最后那道兜底防线在每个字体栈的最末尾我强烈建议放一个通用字体族generic family它告诉浏览器“上面这些你都没有的话你就按这个大类给我选一个默认的吧。”常用的通用字体族有这五个serif衬线字体笔画末端有修饰中文环境通常对应宋体一类。sans-serif无衬线字体笔画末端干净利落中文对应黑体一类。monospace等宽字体每个字符宽度一样适合代码场景。cursive手写体风格。fantasy装饰性艺术字体。实际开发中最常用的是sans-serif和monospace。代码编辑器里、代码展示块里我会用monospace兜底正文和标题一律用sans-serif兜底。这是最保守、最不容易出错的方案。2. 跨平台字体适配实际项目里的最佳方案2.1 为什么同一个网页在Windows和macOS上长得不一样因为两个系统的预装字体完全不同这就是字体渲染差异的根本来源。Windows系统常见自带字体微软雅黑Microsoft YaHei、宋体SimSun、黑体SimHei、Arial、Tahoma、Segoe UI、Consolas。macOS系统常见自带字体苹方PingFang SC、华文黑体STHeiti、华文细黑STXihei、Helvetica Neue、Arial、Menlo、Monaco。Linux系统常见字体比较乱一般有DejaVu Sans、Liberation Sans、文泉驿微米黑、文泉驿正黑但不同发行版差异很大。移动端Android系统有思源黑体Source Han Sans或罗博Roboto的定制版本iOS同样是苹方。因为两边预装字体不一样同一个字体栈在不同系统上命中的字体就不同。合理设计字体栈就是要把这种差异控制在我们可接受的范围内。2.2 我最常用的一套跨平台字体栈综合我这些年的项目经验有一套相对稳妥的组合实测下来在绝大多数系统上表现都不错body { font-family: -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Arial, sans-serif; }看起来很复杂是吧我拆开讲一下每个部分的用意-apple-system苹果私有名称让macOS和iOS的Safari使用系统默认字体效果跟原生app保持一致。BlinkMacSystemFont针对Chrome在macOS上的显示让Mac里的Chrome也走系统UI字体。Segoe UIWindows 10以上系统的默认西文字体兼顾现代感和可读性。PingFang SCmacOS/iOS的中文默认字体在苹果设备上优先命中它。Hiragino Sans GB冬青黑体简体中文用于老版本的macOS覆盖PingFang SC没有覆盖的旧系统。Microsoft YaHeiWindows的中文默认字体保证PC上中文显示为黑体风格。Helvetica Neue老macOS上常见的西文字体兼顾一些没有Segoe UI的旧系统。ArialWindows/macOS都内置的最稳西文无衬线字体保底用。sans-serif最终兜底。这套字体栈的好处在哪它把所有主流桌面操作系统全都兜住了而且英文优先中文其次不会出现“Windows上英文也变成微软雅黑导致英文字距很奇怪”的情况。2.3 中文字体栈要比英文多留几个备胎中文字体有一个特点字体文件大字形数量多很多西文字体里压根没有中文字形。所以你给中文准备字体栈时不光要考虑中文字体各自的特性还要确认前面的西文字体不会“吃掉”中文渲染。一种常见的中文专属字体栈写法body { font-family: PingFang SC, Microsoft YaHei, Hiragino Sans GB, STHeiti, WenQuanYi Micro Hei, sans-serif; }这个栈正确处理了中文场景在macOS上先使用苹方在Windows上使用微软雅黑在Linux上使用文泉驿微米黑如果装了的话最后用sans-serif兜底确保在没有特定中文字体的系统上也能渲染出一个可接受的系统默认黑体。这个写法有一个隐含信息你在用中文优先的策略。如果你希望英文优先显示得更好看就按上一节那套把西文字体放在前面中文字体系接着放后面。2.4 按项目场景来选不是一套走天下给不同项目选字体标准不完全一样。后台管理系统跟操作系统风格一致最重要直接采用刚才那套“-apple-systemSegoe UIPingFang SCMicrosoft YaHei”的系统字体栈几乎零成本维护也省心。展示型官网对外展示的页面品牌调性很重要。这时候可以考虑引入Web Fonts用一款定制字重来强化品牌识别度。电商站点信息密度大转化路径长字体最重要的就是清晰、易读、不干扰用户。系统字体栈足够避免使用过粗的字重影响大段文本阅读。代码类产品单词、代码块要严格用等宽字体否则对齐全乱。给代码块设置font-family: SF Mono, JetBrains Mono, Consolas, Courier New, monospace;。2.5 为什么我不建议直接写某个字体名有些刚入门的朋友喜欢写font-family: 微软雅黑;觉得在Windows上明明显示得好好的为什么还要写一大堆。你单写一个字体的风险是在macOS上微软雅黑没装于是浏览器只能掉到系统默认大概率是宋体英文Times New Roman。你精心设计的页面在Mac上直接变成另一种风格设计还原度全没了。我在实际项目里反复验证过font-family的稳定性取决于整个字体栈的覆盖率而不是某一款字体有多好看。只要覆盖住你用户最常使用的平台结果就差不了。3. Web Fonts与图标字体进阶用法实操3.1 中文字体文件太大Web Fonts要谨慎用网页开发中如果要用一款非系统字体就得通过font-face把它引用进来这就是Web Fonts。国内互联网环境里通常用站内托管或第三方字体平台服务。西文字体做Web Fonts很容易一个字体文件大小通常在50KB~200KB之间一页下来可以接受。但中文字体就不一样了常用汉字几千个光一个字重就动辄几MB这还是在没所有字形都包含的情况下。如果你把一款完整的中文字体做成Web Fonts直接引入页面加载速度会明显恶化移动端更是伤不起。所以现在中文字体做Web Fonts核心思路是“按需提取”也就是子集化subsetting只提取你页面里需要用到的那些汉字做成一个小文件。对于标题字之类的展示性字体效果尤其好。一个简化的子集化思路是先统计页面所有文字内容里的字符集合再通过字体工具例如Fontmin、font-spider把这个子集提取出来最后在CSS里引用。font-face { font-family: MyTitleFont; src: url(./fonts/MyTitleFont.subset.woff2) format(woff2); font-weight: 400; font-display: swap; }然后正常引用h1 { font-family: MyTitleFont, PingFang SC, Microsoft YaHei, sans-serif; }这里需要注意几个坑font-display: swap能保证字体没加载完时先用后备字体显示文字避免白屏。但副作用是字体加载完成后页面会“闪变”视觉上有点跳。font-weight必须和实际字体的字重保持一致。如果字体文件只有400的粗细你却设置font-weight: 700浏览器会去“伪造”加粗效果效果经常很虚。woff2格式在目前主流浏览器里覆盖率已经很高建议优先使用兼容性也很稳。部分老浏览器才需要额外的woff格式。3.2 图标字体本质也是用font-family渲染你可能见过这类写法把图标放进HTML里然后用CSS控制它的大小、颜色.icon { font-family: iconfont, sans-serif; font-size: 20px; }图标字体本质上就是一个特殊的字体文件里面存放的是自定义字形而不是普通文字。使用时通过字形编码来显示对应图标。它在阿里iconfont、BootStrap的Glyphicons等方案里仍然很常见。用图标字体有几个需要注意的地方图标字体的颜色没法在CSS里直接像SVG那样加多色渐变一般只能单色。图标字体内置的字体度量也会影响垂直对齐经常需要在CSS里调整行高或基线位置。现在很多新项目改用SVG雪碧图或SVG图标组件了但图标字体在成熟项目里存量还很大懂它仍然是基本功。3.3 在线字体库与性能之间的取舍如果网站面向的是全球用户直接引入Google Fonts或Adobe Fonts这类在线字体服务很方便。但你要知道在国内访问这些外部字体服务速度不稳定尤其是Google Fonts的域名在部分网络环境下加载很慢。我给国内项目的建议是能自托管就自托管。下载好字体文件放到自己的静态资源目录里通过font-face引用。这样可以稳得住访问速度也不怕第三方服务因网络问题突然罢工。4. 和font-family搭伙的那些字体属性4.1 font-weight不是随便填个数字就能有对应字重font-weight控制字体的粗细可选值有normal、bold、bolder、lighter以及100到900的数值。但很多人忽略了一点不是每个字体都提供了所有权重的字形。比如普通系统默认的微软雅黑实际只有Regular和Bold两个字重文件。如果你设置font-weight: 300浏览器找不到对应粗细的字形文件会把400的字体拉细或变灰来模拟效果通常发虚。所以想用细字重前提是该字体确实带有对应字重文件。使用Web Fonts时尤其要注意按需引入具体字重的文件不要指望浏览器自己去算。4.2 font-style: italic 的“伪斜体”问题设置font-style: italic之后如果当前字体本身没有真正的斜体字形浏览器会把正常字体做倾斜变形这就是“伪斜体”。伪斜体在部分操作系统和浏览器里渲染质量很差汉字尤其明显整体有一种非常生硬扭曲的观感。如果你要强调某段文字建议尽量用加粗、颜色、下划线这些对阅读干扰更小的方式或者选择一款本身包含真斜体的字族。4.3 line-height设置和字体本身有直接关系字体渲染跟行高密切相关。中文字体因为字形结构复杂同样的行高值在不同字体下的视觉间隙差别很大。举个例子同一段文字用微软雅黑和用苹方渲染时相同line-height: 1.6的视觉效果差异很明显苹方往往看起来更紧一些。做设计还原时要结合具体字体去调行高不要死记“1.5倍行距就通用”。另外line-height的单位也值得注意。在块级元素上用无单位的数值比如1.5可以让后代元素继承“比例”而不是固定的像素值这是更灵活安全的写法。避免在body上写固定px行高然后子元素想调整却处处被限制。4.4 字体相关的缩写属性新手最容易栽跟头font是一个缩写属性可以一次性设置font-style、font-variant、font-weight、font-size、line-height和font-family。比如p { font: italic 600 16px/1.8 PingFang SC, Microsoft YaHei, sans-serif; }它要求font-size和font-family必须同时出现顺序和格式也有严格限制写错了整个属性会失效。我实际见过不少同学因为没写全被浏览器整个忽略排查半天才发现是font缩写的问题。我的建议是在小范围、你能确定属性值时才用font缩写全局基础设置还是分开写font-size、font-weight、font-family更清晰更好维护。4.5 浏览器默认字体和它背后的“白底黑字”习惯每个浏览器都有一份默认样式表里面会给html或body设置一个默认字体集合通常是基于当前操作系统的默认字体来定的。这解释了为什么你什么都不写页面也能显示文字——因为浏览器已经替你指定了一个默认字体族。但这份默认字体在每个平台都不一样所以“不写font-family”就等于把页面效果完全交给了浏览器根本无法跨平台保持一致。这也是为什么做页面第一步就是先给body统一个字体。我习惯在CSS开头做一份类似“reset”的操作html, body { font-family: -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }-webkit-font-smoothing: antialiased这个属性也挺关键macOS上默认的字体平滑方式会让细字体显得偏粗设成antialiased之后小号字体观感更清晰。不过要注意这个属性只在macOS/iOS的WebKit内核浏览器里生效Windows上没有效果。5. 实操过程中的常见坑与排查思路5.1 字体没生效第一反应别改代码先查这些遇到字体显示不对我现在的排查顺序基本是固定的打开浏览器开发者工具选中目标元素看Computed计算样式里最终的font-family到底是哪个。先确认CSS真生效了再谈字体本身。确认当前操作系统的字体列表里是否真的装了CSS里写的那款字体。Windows可以用“设置→字体”查看macOS用“字体册”查看。检查CSS里字体名是否写对尤其是空格、大小写、中文名与英文名混用的情况。看是不是被另一个更具体的选择器覆盖了。CSS优先级规则比想象中更容易出问题比如class选择器写了很多层最后实际生效的是另一个组件样式里的font-family。确认有没有全局样式把字体重置了比如一些UI库自带的reset。这五步走完绝大多数“字体不对”的问题都能定位。5.2 中文字体死活不生效大概率是这个问题中文环境里有一个非常微妙的坑部分浏览器对中文字体查找依赖的是字体安装时注册的字体名称而不是文件名。举个例子你下载了一款字体文件名叫SourceHanSansSC-Regular.otf但它在系统里注册的字体名称是思源黑体。如果你在CSS里写Source Han Sans SC有的系统能匹配上有的就不能全看字体内部的命名表。遇到中文自定义字体不生效时可以用操作系统字体管理工具查看字体“官方名称”把那个名字抄进CSS里别自己临时翻译。5.3 字体之间频繁切换导致页面闪烁页面里同时加载多款Web Fonts尤其还设了font-display: swap用户打开页面时会先看到后备字体等字体文件加载完了再一下子切换成目标字体视觉上就是一阵闪烁。这在慢网络下尤其明显。解决这个问题的常用手段合理精简字体数量业务系统一两个字族足够。使用preload预加载字体文件让浏览器更早发起请求。考虑使用font-display: block虽然会有短暂空白但避免了字体加载完成后突然出现的整体布局跳动。没有绝对完美的方案最终还是要看你的业务对首屏速度和视觉稳定性的取舍。我个人习惯是正文尽量用系统字体Web Fonts只留给标题或品牌字体这样闪烁的影响被限制在最小范围内。5.4 为什么字体大小一样但看起来高矮胖瘦差很多不同字体的实际视觉尺寸和它们在字体度量metrics里定义的上下边界强相关。同样设置font-size: 16pxArial渲染出的文字看起来大概占13px的实际可见高度而有的字体可能视觉上更饱满或更瘦。所以你在设计稿里看到的16px和实际网页里某些字体渲染出的16px视觉观感可能有差异。这不是bug是字体本身的设计度量决定的。碰到这种问题不要死磕字号试着加一点line-height、调整padding或重新审视字体选择效果可能反而更好。5.5 英文和中文混合排版容易忽略的基线对齐中英文混排时因为两种文字的基线规则不一样会把行高和对齐弄得很别扭。常见表现是一行文字里中文和英文的底边明显不在一条水平线上。没有特别完美的CSS属性一键解决我常用的小技巧是给英文单独设置一个字体和内边距微调或者vertical-align配合调整。如果情况不复杂直接让中文和英文共用一套字体栈让中文的默认基线对齐方式去覆盖通常能得到可接受的效果。5.6 看看别人怎么踩坑比我怎么说都管用前端社区里GitHub上有不少维护得很好的CSS字体栈项目例如system-font-css、font-family-repository这类资源。它们收集了不同平台下推荐的系统字体栈写法。你可以直接参考这些项目里的写法结合自己业务的用户画像挑出适合自己的一套组合。我自己的做法是把项目面向的主流平台记下来然后构建一个覆盖它们的字体栈再三确认每种平台下至少有一个字体能命中。这样既不会堆一大堆永远用不上的字体名又不会漏掉目标平台。个人实操中积累的几个经验先说个人最直观的感受font-family没有“绝对正确”的答案只有“最适合当前项目”的方案。系统字体栈在大多数业务下就是够用的花里胡哨的Web Fonts反而容易拖慢页面。做技术选择时优先保证可读性和加载性能再考虑设计表现力。再分享一个小技巧我在给多个子模块设置字体时喜欢把字体栈抽取成一个CSS变量:root { --sans-font: -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Arial, sans-serif; --mono-font: SF Mono, JetBrains Mono, Consolas, Courier New, monospace; } body { font-family: var(--sans-font); } code, pre { font-family: var(--mono-font); }这样做的好处是如果团队决定统一换字体或者某个系统环境的字体坑发现了新的适配策略只需要改这一处全站生效。维护成本大大降低也避免了在不同组件里各写一套字体栈导致后面改起来找不到地方。最后再提一个容易被忽略的点写完字体样式后一定要在多个设备上过一遍。Windows桌面、macOS笔记本、Android手机、iOS手机各看一眼把字体栈里每个平台能命中的字体都验证一遍。这个过程花不了多少时间但能避免很多发布后才发现的水土不服。