
翻别人的样式表最容易看出功底的地方往往不是布局写法而是font-family那一行。有人一口气拖了十几个字体名从开源黑体排到系统自带字体再排到通用族有人只写一个sans-serif就交了差。两种极端都能把页面跑起来但跑起来和跑得好之间差的是对字体匹配规则的理解。样式表中常用字体这件事表面上是选择题实际上是匹配题——你写的每一个字体名都是一次尝试浏览器会按顺序拿去试穿穿不上就换下一件。搞清楚这个试穿流程很多我明明写了字体为什么没生效的困惑会瞬间消失。这篇内容适合刚接触样式表不久、被字体回退搞得头晕的人也适合做了几年但一直靠复制粘贴字体栈、没认真梳理过的人。我会从匹配机制讲到字体栈的分层设计再讲中文字体的体积账、加载策略、字重陷阱和我在实际项目里踩过的坑尽量给出可以直接抄走的结构和判断依据。1. 字体写在样式表里为什么经常看起来没生效刚开始写样式的人几乎都经历过这一幕明明在样式表里把字体设成了设计稿标注的那一款本地浏览器预览也对了上线之后换台电脑一看字形完全变了。第一反应是样式被覆盖了翻半天 devtools 发现font-family确实生效了控制台也没有报错字体就是不对。这类问题十有八九不是样式写错而是对字体的匹配过程理解有偏差。1.1 字体匹配是逐个字符去找的不是整段一起换很多人脑子里对font-family的理解是整段文字用哪个字体这个理解在纯英文环境下勉强成立在中英混排环境下就不成立了。真实机制是浏览器把文本拆成一个个字符针对每个字符去字体栈里从头找找到第一个包含该字符字形的字体就用它找不到就继续往后试全部失败才交给通用族兜底。这个机制解释了太多现象。比如你写了一段中文字体栈第一位是一款纯拉丁字体结果中文并没有变回默认而是很好地显示出来了——因为那款拉丁字体里根本没有汉字字形浏览器对每个汉字都跳过了它自动落到了后面的中文字体或者系统默认字体上。反过来如果第一位是一款什么都包的字体中文、英文、标点、数字全都被它接住你就失去了对数字和标点形态的控制权。还有一类很典型的现象一段中文里夹了一个特殊的数学符号或者箭头整个段落的这一小块突然变成了另一种字形粗细和基线都对不上。这就是字符级回退的结果——主字体里没这个字形浏览器临时从别的字体借了一个过来借来的字形度量不一致视觉上就跳出来了。知道这一点之后遇到混排里个别字符字形突兀第一反应不该是调样式而是去查这个字符在字体里的覆盖情况。1.2 通用族关键字是你最后的兜底但兜底结果不由你决定几乎所有人都知道字体栈要以sans-serif、serif或者monospace这类通用族结尾但很多人并不清楚它们到底意味着什么。通用族不是某个具体字体而是一个交由渲染环境决定的占位符。你写了sans-serif浏览器会拿它所在平台配置的默认无衬线字体来填不同平台、不同系统版本填进来的东西可以完全不同。这意味着通用族能保证一定有字体可用但保证不了长得一样。它的价值在于兜底不在于控制。如果设计稿对字形有明确要求通用族只能保证不难看保证不了还原度。我在实际项目里见过一种写法字体栈里前面几个都是自定义字体最后用serif结尾结果某款自定义字体加载失败时页面里的正文整体变成了衬线体和旁边还在用无衬线的小标题形成强烈反差用户截图来问是不是样式坏了。所以兜底族的选择要和主字体保持同一气质主字体是黑的兜底就用无衬线主字体是宋的兜底才用衬线。通用族关键字语义典型使用场景兜底气质serif衬线体长文阅读、书籍排版感偏传统、偏印刷sans-serif无衬线体绝大多数界面正文、按钮、表单现代、中性monospace等宽体代码、日志、数据表格、金额对齐技术感system-ui系统界面字体需要贴合操作系统原生观感的界面与平台一致ui-monospace系统等宽字体代码块希望与系统终端观感统一与平台一致cursive手写风格装饰性文案、少量强调不可控慎用fantasy装饰体极少数趣味场景不可控慎用表格里不可控那两行不是危言耸听这两类通用族在不同平台上的实际映射差异极大用在正式产品里基本等于放弃控制权。真要手写风格老老实实用自定义字体别指望通用族。1.3 devtools 里看到的已应用字体可能是假的排查字体问题时很多人依赖开发者工具里那个Rendered Fonts面板看到显示了某款字体就以为问题解决。这里有个细节要注意这个面板统计的是当前选中元素内实际参与渲染的字体集合一个元素里如果混排了中英文往往会列出好几个字体名。你看到主字体在列表里不代表所有字符都用了它。更稳妥的排查方式是把可疑字符单独拎出来复制到一个只设置了单一字体的测试元素里观察字形变化再逐项加减字体栈里的条目做二分定位。这个方法笨但对到底哪一条在起作用这种问题特别有效。我遇到字体栈长到十几条的时候基本都用这个办法从中间一切两半看哪一半里藏着罪魁祸首。提示排查字体问题前先确认文本本身没有携带内联样式或富文本粘贴残留的字体声明。从文档工具里粘贴过来的内容经常自带一层行内字体设置优先级高于你的样式表这类问题在 devtools 里看到的就是样式表明明写了却没生效。2. 一条靠谱的字体栈该怎么分层把字体栈当成一串随手堆的字体名是绝大多数字体问题的源头。真正好维护的写法是分层每一层职责明确层与层之间有清晰的退化逻辑。我现在的习惯是把字体栈拆成拉丁层 中文层 通用兜底三段用自定义属性拼起来既不重复又方便整体替换。2.1 拉丁层放前面是有前提条件的拉丁字体放在字体栈靠前的位置目的是接管英文、数字和西文标点的字形。这个做法的前提是这一层里的字体必须是纯拉丁字体或者至少不含汉字。如果往这一层塞了一款带完整汉字字库的字体那后面的中文层就永远轮不上了你精心挑的中文字体等于白搭。数字是最容易被忽略的部分。很多界面里数字出现频率极高——价格、时间、统计卡片、表格——而不同字体里数字的字宽差别相当明显。等宽数字和比例数字混在一起表格里的小数点就对不齐。我在做一个数据看板的时候因为拉丁层第一位字体用的是比例数字金额列的个位和十位宽度不一样整列右对齐之后小数点参差不齐看着特别粗糙。后来把这一层换成数字等宽的字体问题直接消失。所以选拉丁层字体时除了看字母造型一定要单独确认数字的字宽策略。英文和中文的基线、字高、重心位置也有差异。Latin 字体的 x-height 通常偏小中文字体的视觉重心偏中两者叠在一行里如果行高设置有紧容易出现英文浮在上面、中文压在下面的观感。这不是 bug是两个字体度量体系不同的正常结果能调的只有行高和垂直对齐策略。2.2 中文层的关键在于只放一款中文层的写法有个反直觉的建议不要堆太多中文字体名。原因很实际——中文字体文件动辄几兆堆三四款中文字体既不会让加载更快也不会让兜底更靠谱只会让字体栈变得难以维护。正常情况下中文层放一款主字体就够了剩下的事情交给通用族。如果确实需要区分场景比如正文用一款、标题用另一款正确做法是定义两套自定义属性在具体选择器上分别引用而不是在同一个字体栈里堆砌。我一般会这样组织:root { /* 拉丁层只放纯拉丁字体避免抢占汉字字形 */ --font-latin: system-ui, Helvetica Neue, Arial; /* 中文层只放一款主字体名字换成项目实际选定的那一款 */ --font-cjk: your-cjk-font; /* 正文栈拉丁 → 中文 → 通用兜底顺序不能乱 */ --font-sans: var(--font-latin), var(--font-cjk), sans-serif; /* 标题栈可以换一款更有性格的中文标题字体 */ --font-display: var(--font-latin), your-display-font, sans-serif; /* 代码栈全链路等宽 */ --font-mono: ui-monospace, your-mono-font, monospace; } body { font-family: var(--font-sans); } code, pre, kbd, samp { font-family: var(--font-mono); }这段代码里有个细节值得单独说通用兜底sans-serif必须放在整个列表的末尾不能塞进--font-latin里。我见过不少写法把sans-serif写在拉丁层内部拼起来之后就变成拉丁字体 → 通用族 → 中文字体通用族会先一步接住汉字中文层直接失效。这种错误特别隐蔽因为页面看起来一切正常只是你选的中文字体根本没被用上。2.3 等宽字体栈要单独维护别和正文混用等宽字体栈是完全独立的一套东西别想着在正文栈里顺手带上等宽字体。等宽的使用场景很明确代码块、日志输出、终端式界面、需要按字符对齐的表格、行内代码标记。这些场景对字宽一致性的要求远高于对字形美观的要求。中文环境下的等宽有个坑中文本身就是方块字天然等宽但中英文混排时一个汉字宽度通常等于两个西文字符宽度这叫双宽。如果你的等宽西文字体不是严格的半宽设计中英混排的代码注释就会错位。我处理这类问题的办法是给代码块单独设一套行高和字距并且在代码块里尽量避免中文注释和英文代码混在同一行做对齐能用独立行就用独立行。还有一点ui-monospace这类系统等宽族在桌面端表现稳定在部分移动端可能映射不到合适的等宽字体最终落到普通无衬线体上代码就失去了等宽特性。真要保证效果就得配一款明确的等宽字体作为第一优先级系统族放后面。pre { font-family: var(--font-mono); font-size: 13px; line-height: 1.7; font-variant-ligatures: none; /* 关掉连字避免 ! 被渲染成单个符号 */ tab-size: 2; }font-variant-ligatures: none这一行是我踩过坑之后加的。有些等宽字体会把!、、-这类组合渲染成连字符号在代码展示场景里会造成误读尤其是让读者以为源码里写的是单个特殊字符。展示源码时关掉连字是更稳妥的选择。3. 系统字体和自定义字体到底该怎么选这是字体决策里绕不开的一道选择题。用系统自带字体零请求、零等待、加载完成后立刻渲染用自定义字体字形可控、设计还原度高但要付出加载成本还得处理加载过程中的闪烁和回退。这两者没有绝对优劣关键看你这个页面是内容优先还是品牌优先。3.1 系统字体的隐性成本在跨平台一致性上系统字体的最大好处是不用下载。用户的操作系统里本来就有这些字体样式表里写上名字渲染时直接取用首屏不会因为字体加载而出现任何延迟。对于管理后台、内部工具、文档站这类以效率和可读性为第一诉求的产品系统字体栈几乎是标准答案。代价是一致性。同一份样式表在不同操作系统上落到不同的实际字体上字宽、字高、行高全都不同。这个差异在纯文本场景下不明显但在对像素敏感的组件上会暴露得很彻底按钮里的文字水平居中靠 padding 控制字体一换宽度变了居中就偏了标签页的固定宽度容器里字体一换文字就换行了图标和文字间距用 margin 控制的字体一换视觉间距就变了。我在一个多端项目上吃过这个亏。同一个按钮组件在一台机器上文字刚好填满在另一台机器上文字溢出了两个像素触发了overflow: hidden最后两个字被切掉了一点。原因是那台机器的默认无衬线字体字宽略大。修复方式不是去调字体栈而是把按钮宽度从固定值改成由内容撑开加最小宽度从根上消除对字体度量的依赖。这件事让我形成了一个习惯凡是尺寸和字体度量强相关的组件一律用弹性布局而不是固定尺寸字体再怎么变都不会崩。3.2 自定义字体的体积账得算清楚再决定中文字体的体积问题是每个做中文界面的人都必须面对的。西文字体通常几十到几百 KB中文字体因为字形数量庞大全字库动辄几 MB 甚至十几 MB。直接把全字库丢到线上等于给首屏加了一个巨大的负担尤其在网络条件一般的情况下用户会先看到默认字体渲染的页面几百毫秒甚至几秒之后字体加载完成页面文字跳变一次体验很差。解决路径是子集化只保留页面实际用到的字符把字体裁成一个精简版本。常见做法是先统计项目中出现的字符集合再生成对应子集。一个内容型站点常用的三千多个汉字子集化之后通常能压到几百 KB 甚至更小如果只是固定文案的落地页几百个字符的子集能压到几十 KB。方案字符规模典型体积量级适用场景主要风险全字库直接引入两万以上数 MB 起极少使用首屏严重拖慢字体跳变明显常用字子集三千左右数百 KB内容为主的站点、后台生僻字回退到兜底字体页面级子集数百数十 KB固定文案的落地页、活动页内容变更需重新生成按区块分片分片若干每片数十 KB长文、多模块站点构建流程复杂需维护分片映射系统字体00工具类、后台类产品跨平台观感不一致按区块分片是个值得展开的做法把字体按字符使用频率或者按页面模块切成若干片用unicode-range声明每一片负责哪些字符区间浏览器只下载当前页面实际需要的那几片。这个方案的收益在长文档站点上很明显代价是构建流程要维护一套字符分片规则改动内容时子集要重新生成属于前期投入大、后期省心的类型。注意字体文件涉及授权范围。下载来的字体、系统里自带的字体、开源协议的字体三者的商用授权条件完全不同系统自带字体通常不能随应用分发开源字体也要看清协议对嵌入和商用的具体要求。字体授权问题在产品上线前一定要确认清楚这类问题一旦出现处理成本远高于换一款字体。3.3 font-display 该怎么取值取决于你更怕哪种难看自定义字体必然有一个还没加载完的时间窗口font-display就是用来定义这个窗口内怎么处理的。它不改变加载速度只改变等待期间的表现所以选值的本质是选你更能接受哪种难看。swap是最常用的选择先用兜底字体立即渲染文字字体加载完成后替换。用户能马上读到内容代价是会发生一次字形跳变行高变化明显的时候整页内容会位移一下观感不算好但内容可用。block相反先不显示文字等字体加载完再显示最多等一小段时间超时之后同样走兜底。这个方案在品牌页上能避免跳变但会出现文字短暂消失对可访问性和感知性能都不友好。optional是我个人比较偏好的一个它给字体一个很短的加载窗口在这个窗口内加载成功就用自定义字体加载不成功这次就直接用兜底字体不做替换。跳变问题彻底消失代价是网络慢的用户可能永远看不到你精心挑的那款字体。对于内容优先、字体只是锦上添花的站点这个取舍很划算。fallback介于block和swap之间等待窗口很短超时后用兜底渲染之后如果字体加载完成仍然会替换。实际用下来它和swap的差异在日常网络条件下不太明显更多是给极端情况留一点缓冲。取值等待期表现是否替换主要代价适用倾向auto由浏览器决定视情况行为不可预期不建议显式使用block文字不可见是首屏出现空白文字品牌视觉优先swap兜底字体立即渲染是字形跳变、布局位移内容可读性优先fallback极短隐藏期是介于两者之间折中方案optional兜底字体立即渲染否慢网用户看不到自定义字体稳定性优先预加载也值得提一句。给字体加preload能提前发起请求缩短等待时间但这招只对首屏一定用得到、体积不大的字体划算。给一款几百 KB 的中文字体加预加载等于把它提到和关键样式同等优先级反而会挤占首屏其他资源的带宽。我的经验是拉丁字体可以预加载中文主字体谨慎预加载按区块分片的字体不要预加载让浏览器按需去取。4. 字重和字形属性最容易写出假粗体的地方字体栈定下来之后很多人以为字体的事情就结束了实际上字重属性写错导致的视觉问题一点都不少而且比字体选错更难发现因为文字确实变粗了只是粗得不对。4.1 数值字重不是随便填的字体里没有就会就近匹配font-weight可以写关键字也可以写 100 到 900 的数值。关键字normal对应 400bold对应 700这个大家都知道。问题出在数值上不少人觉得 500、600 这种中间值是更精细的控制随手就写。但如果字体文件里根本没有 500 这个字重浏览器会按匹配算法去找最接近的可用字重你写的 500 可能被渲染成 400也可能被渲染成 700具体取决于字体提供了哪些字重以及匹配规则。不同字体提供的字重数量差别很大。有些字体只有 400 和 700 两档有些提供 300 到 900 的完整区间还有可变字体可以在整个区间内连续调节。在只有一个字重的字体上写font-weight: 500和写 400 完全没区别在只有 400 和 700 的字体上写 600多数情况下会落到 700 上看起来比预期的更重。我的做法是先确认项目主字体实际提供了哪些字重然后只在这几个字重里做设计需要中间层次的视觉效果就靠字号、颜色、字距来区分而不是硬写一个字体里不存在的字重。真要完整控制就换可变字体用font-variation-settings或者直接写数值font-weight浏览器会在可变轴上连续插值不存在落到最近一档的问题。写下的值字体仅有 400/700字体有 300–900可变字体400精确命中精确命中精确命中500落到 400精确命中连续插值600落到 700精确命中连续插值800落到 700精确命中连续插值100落到 400落到 300连续插值这张表能解释一个常见困惑为什么在本地开发环境里 600 看着刚好到了另一台机器上就变成明显的粗体。因为两台机器上实际生效的字体文件提供的字重档位不一样匹配结果自然不一样。4.2 合成粗体和合成斜体能关就关还有一种更隐蔽的情况字体文件里一个粗体都没有但页面上的粗体效果依然正常显示。这是浏览器在做合成加粗——把正常字重的字形描边加粗硬造出一种粗的效果。这种合成粗体的观感通常很差字形的笔画会粘连、间距会变形、小字号下尤其糊。斜体也是同理。字体里没有真正的斜体字形时浏览器会把正体倾斜一个角度来模拟这种合成斜体的字母形态并没有经过重新设计看起来非常假在正规排版里是不能接受的。关闭合成的写法是font-synthesis: none代价是没粗体就是没粗体、没斜体就是没斜体你需要自己去保证字体文件提供了对应的字形。我的建议是正文区域的合成粗体可以关掉逼自己把字重配齐标题区域的加粗如果只是用来强化层级那不如直接用字号和颜色来做别依赖合成效果。body { font-family: var(--font-sans); font-synthesis: none; /* 禁用合成粗体与合成斜体 */ } strong, b { font-weight: 700; /* 前提是主字体确实提供了 700 */ } em, i { font-style: italic; /* 主字体需提供真斜体否则考虑改用其他强调方式 */ }中文环境里还要多留一个心眼汉字本身没有严格的斜体传统很多中文字体的斜体要么是直接倾斜要么干脆缺失。中文强调用加粗、换色、加底纹都比斜体稳妥这是中文排版和西文排版的一个实际差异直接照搬西文排版习惯容易出问题。4.3 字号、行高、字距这三者比字体本身更影响观感很多人把字体不好看归咎于字体选得不对实际上调一调字号和行高同一款字体的观感能有肉眼可见的提升。中文字形结构复杂笔画密度高字号太小会糊成一团字号太大又显得笨重。正文用 14px 到 16px 是比较稳的区间行高在 1.6 到 1.8 之间长文可以再松一点。行高的写法有个细节line-height: 1.6这种无单位写法是基于元素自身字号计算的会层层继承下去而line-height: 24px这种带单位的写法子元素会直接继承这个固定值。在组件复用的场景里无单位写法更安全子元素字号变化时行高会跟着自适应不会出现大字号配小行高的挤压问题。字距方面西文和中文的需求完全不同。西文适当收紧一点会显得紧实中文基本不需要调整字距加了反而显得松散。如果确实要给标题做什么特殊处理用letter-spacing单独针对标题元素设置别往全局丢。5. 排查字体问题的完整链路和几个真实坑字体问题的排查有个特点它不报错。控制台干干净净样式面板显示一切正常页面就是不对劲。这种静默失败最容易消耗时间所以有一套固定的排查顺序非常重要能避免在错误的方向上反复试。5.1 字体不生效的四步定位法我现在的排查顺序固定为四步基本能覆盖九成以上的情况。第一步确认选择器命中的是不是你以为的那个元素。这一步听起来很基础但确实是最常见的原因。用元素检查工具点中目标文字看右侧面板里生效的font-family是哪一条规则、来自哪个文件的第几行。如果命中的是另一个选择器那问题跟字体本身无关是样式优先级的事。第二步确认字体文件是否真的加载成功了。在资源面板里按类型筛出字体请求看状态码和大小。常见情况是字体文件路径写错、文件名带哈希但引用没更新、跨域配置缺失导致字体被拦截。字体加载失败时页面不会有任何报错提示只是默默用兜底字体所以这一步必须主动去看。第三步确认实际参与渲染的字体是哪个。前面提过这个面板在混排场景下会给出一串字体名需要结合具体字符判断。如果发现某一段文字用的不是主字体基本可以判断是字符级回退去查这段文字里有没有主字体不包含的字符。第四步确认编码和字符本身。有些从文档工具里复制过来的文本看似是普通空格实际是全角空格或者不换行空格这类字符在某些字体里缺失会触发回退导致局部字形突变。还有文本里混入了零宽字符的情况肉眼完全看不出来但会影响排版。把可疑文本复制到纯文本编辑器里过一遍或者用一个只含已知字符的测试字符串替换能快速排除这类干扰。5.2 跨平台渲染差异导致的对齐错位这是我在实际项目里被折腾得最久的一类问题。同样的样式在两台机器上跑垂直居中的文字一高一低差一两个像素。查了半天发现根源在行高的默认值上line-height写normal的时候实际行高由字体自身的度量数据决定不同字体的升降部比例不同算出来的行高就不同垂直居中自然就偏了。修复方式很直接不要在依赖垂直对齐的地方用normal行高给一个明确的无单位行高值再配合弹性布局的居中让对齐不再依赖字体度量。这个改动看着不起眼但把一个组件从在某台机器上刚好变成在任何机器上都稳是很有价值的一步。.button { display: inline-flex; align-items: center; justify-content: center; font-family: var(--font-sans); font-size: 14px; line-height: 1.5; /* 不用 normal避免继承字体度量 */ padding: 8px 16px; min-width: 88px; /* 用最小宽度而不是固定宽度 */ }还有一个相关的问题中英混排时如果英文部分用了字号更小的字体基线对齐会出现偏差。vertical-align可以调但更根本的办法是让混排的两部分共享同一套行高和字号策略别在同一个文本流里塞不同字号的内容。真需要做上标下标这类效果用专门的排版标签配合单独的样式不要靠字号硬凑。5.3 打印和导出场景里字体会莫名其妙地丢这个坑比较冷门但只要产品里有打印或导出为文档的功能迟早会遇到。页面在浏览器里看着完全正常打印预览一打开字体全变了或者某些字直接变成方块。原因通常有两个。一是打印样式表没有继承主样式表的字体设置打印时走的是另一套默认值二是一些自定义字体在打印渲染管线里不被支持或者为了减小打印文件体积被跳过最终回退到系统字体。处理办法是给打印场景单独写一套字体回退优先用系统字体保证一定能渲染出来别在打印样式里硬指自定义字体。同时检查一下打印样式表有没有被正确地加载很多时候打印样式文件是空的或者根本没被引入这种情况下所有打印相关的问题都无从谈起。media print { body { /* 打印场景不依赖网络字体直接用系统字体保证一定渲染 */ font-family: system-ui, sans-serif; font-size: 11pt; line-height: 1.6; } pre, code { font-family: monospace; } }导出场景还要多考虑一层导出的文档在别的设备上打开那台设备上未必有你的字体。这种情况要么把字体嵌入到导出文件里要么在导出前就把文字轮廓化但轮廓化之后文字就不可选中、不可搜索了对需要复制内容的场景很不友好。我的取舍是内容为主的导出用系统字体保证普适视觉为主的导出才考虑嵌入字体。5.4 字体变更时的迁移成本别等到上线前一天才发现最后说一个流程上的经验。字体这种东西改起来看着只是换一行实际影响面极广所有依赖字体度量的固定宽度、所有手工调过的间距、所有用了图标字体的地方都会跟着变。我现在的做法是把字体栈强度集中在自定义属性里全站只允许通过那几个变量引用字体任何地方都不写死字体名。这样换字体只需要改一处影响范围可控也方便做灰度对比。同时给字体变更留出回归清单正文段落的行数是否变化导致布局撑高、按钮文字是否溢出、表格列宽是否需要重算、图标字体是否还有对应字形、导出和打印是否正常。这份清单过一遍能挡掉大部分字体迁移带来的连锁问题。6. 一套可以直接落地的字体配置骨架把前面这些内容收拢一下我给出一套我实际在用并且在多个项目里验证过可用的配置骨架。它的核心思路是分层、集中、可替换所有字体决策集中在根元素的自定义属性里组件只引用变量不写具体字体名通用兜底永远放在最后。:root { /* ---------- 字体栈分层 ---------- */ --font-latin: system-ui, Helvetica Neue, Arial; --font-cjk: your-cjk-font; --font-cjk-display: your-display-font; --font-sans: var(--font-latin), var(--font-cjk), sans-serif; --font-display: var(--font-latin), var(--font-cjk-display), sans-serif; --font-mono: ui-monospace, your-mono-font, monospace; /* ---------- 排版尺度 ---------- */ --leading-tight: 1.3; --leading-normal: 1.6; --leading-loose: 1.8; } html { font-family: var(--font-sans); font-size: 16px; line-height: var(--leading-normal); -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; } h1, h2, h3 { font-family: var(--font-display); line-height: var(--leading-tight); font-weight: 700; } pre, code, kbd, samp { font-family: var(--font-mono); font-synthesis: none; } /* ---------- 自定义字体的加载策略 ---------- */ font-face { font-family: your-cjk-font; src: url(/fonts/your-cjk-font-subset.woff2) format(woff2); font-weight: 400; font-style: normal; font-display: swap; /* 中文子集按使用频率分片 */ unicode-range: U4E00-9FFF, U3000-303F, UFF00-FFEF; } media print { html { font-family: system-ui, sans-serif; font-size: 11pt; } }-webkit-font-smoothing: antialiased这行在不同平台上的效果并不一致它在部分渲染环境下会让字重看起来偏细。用不用取决于你主字体的实际表现建议实际对比之后再决定别当成必须加的一条。看起来更细腻和看起来更薄更糊之间往往只差一个字号本地测试的时候多换几个常见字号看看。text-rendering: optimizeLegibility现在的作用已经很小了主流浏览器在默认设置下已经处理得不错加上它偶尔还会带来连字相关的意外我现在的习惯是能不写就不写。这类看起来能提升排版质量的属性实际收益经常被高估副作用却容易被忽略。最后分享一个我在调整字体时常用的笨办法把页面里所有文字内容截图保存一份改完字体之后再截一份两张图并排看。人眼对单个字形的变化不敏感但对整体版式的变化极其敏感并排对比能立刻发现行数变化、间距失衡、对齐错位这些细节。这个方法比盯着样式面板改参数有效得多尤其是在换字体的最后阶段做验收的时候。