
1. 网站中文字体替换到底难在哪里如果你做网站开发超过一两年大概率绕不过这个坎中文字体实在太大了。西文字体一个文件几百KB已经算大的中文字体随便一个完整字库就是几MB甚至十几MB。我见过不少个人博客因为直接挂了一个完整版思源宋体首屏加载直接多出8MB的流量移动端打开卡得用户直接关页面。所以“换中文字体”这件事从来不是“把字体文件丢进CSS里”这么简单它牵扯到加载策略、格式兼容、渲染性能、版权合规甚至还有你到底该走CDN还是该把文件放到自己服务器上的路线选择。这篇文章我会把两条路线都完整走一遍一条是通过CDN引入一条是直接引入也就是把字体文件放在自己站点目录下通过font-face声明加载。我会结合自己实际做过的几个项目把两条路的优缺点、适用场景、具体配置、还有那些文档里不会写的坑都讲清楚。适合谁来读如果你是个人博客站长、前端开发、独立开发者想给网站换一套好看的中文字体又担心影响性能和兼容性这篇文章可以直接当参考手册用。我尽量用大白话讲原理同时给出可以直接抄的配置代码。先说结论两条路没有绝对的好坏。CDN引入最省事但可控性和国内访问速度经常出问题直接引入前期麻烦一点但一旦配置好稳定性和性能反而更好。接下来我按技术细节慢慢拆。2. 方案的底层逻辑字体格式、加载策略与两种引入方式的本质区别2.1 中文字体为什么这么大格式怎么选在聊引入方式之前得先把字体本身的“物理特性”搞清楚。一个中文字体文件动辄好几MB是因为常用汉字就有三千多个加上标点、生僻字、扩展字集字形轮廓数据量自然就上去了。英文26个字母加上数字符号也就一百多个字形跟中文完全不是一个量级。所以做中文字体替换第一件事就是格式选型。目前Web端主流的字体格式有这几种TTFTrueType最通用的格式Windows、macOS、Linux都能直接安装浏览器支持也比较完整但体积大压缩率低。OTFOpenType可以包含PostScript轮廓某些特殊字体的高级排版特性更好但Web端兼容性和TTF差别不大体积也大。WOFF专门为Web设计的格式本质是TTF/OTF加压缩体积明显缩小IE9、Chrome、Firefox等主流浏览器都支持。WOFF2WOFF的升级版采用Brotli压缩体积比WOFF再小30%到50%目前除了老掉牙的浏览器几乎全线支持。我的建议很简单能用WOFF2就用WOFF2并准备一个WOFF或TTF做老浏览器回退。如果你从某处下载的字体是TTF格式直接丢到网站上也能用但不推荐——同样的字体TTF可能5MB转成WOFF2后可能只有2MB多这差距对网页加载是致命的。提示字体格式转换可以用fonttoolsPython库或者在线工具比如 transfonter.org、fontsquirrel.com 的 webfont generator。我个人更推荐本地用 fonttools因为在线工具偶尔会破坏字体内部的排版信息尤其是处理一些商业字体时。2.2 CDN引入和直接引入的本质差异表面上CDN引入和直接引入只是字体文件存放位置不同但实际影响面很广。CDN引入的本质是“走别人的服务器资源、借别人的带宽分发”。引入方式通常有两种一种是用现成的公共字体CDN服务比如 Google Fonts、jsDelivr 上别人打包好的字体仓库另一种是你自己把字体传到 GitHub、又拍云、七牛云等对象存储然后通过CDN加速域名对外分发。无论哪种字体文件都不在你的网站服务器上。直接引入的本质是“自托管”字体文件放在你的网站目录下和图片、CSS、JS一样由你的服务器或你当前使用的托管平台比如 Vercel、Netlify、GitHub Pages直接吐给用户。这两者有几个层面的差异对比维度CDN引入直接引入接入成本极低引用一行URL即可需要下载、格式化、配置前期繁琐加载速度依赖CDN节点质量和链路依赖自己服务器带宽/托管平台速度稳定性公共CDN可能有地域问题或服务变动完全可控但服务器挂了字体也没了隐私合规用户请求字体时会请求第三方服务器无第三方请求更利于合规审计许可证适配部分开源字体允许商业字体要谨慎同样要谨慎但更容易做本地授权管理自定义能力受限于服务商提供的字体任意字体任何字重子集都能配你可能会问既然直接引入这么可控为什么还有那么多人在用CDN因为省事。对于个人博客、内容站或者你只是临时换个字体看看效果CDN引入简直是解放劳动力。但一旦涉及到性能优化精细化控制、商业项目授权、或者国内用户特别多的情况直接引入几乎必然胜出。2.3 渲染机制为什么换字体时页面会闪一下这里必须补一个几乎所有教程都会忽略的点——字体的加载和渲染机制。浏览器遇到CSS里声明的font-family时并不会立刻下载对应的字体文件而是先按当前系统默认字体渲染文字等字体文件下载完成后再“偷偷”替换成目标字体。这就引发了两种常见现象FOITFlash of Invisible Text浏览器在字体下载完成前直接把文字隐藏下载完了才显示。用户会看到一片空白体验极差。FOUTFlash of Unstyled Text浏览器先用回退字体渲染字体到位后再切换。用户会看到字体“闪变”但至少内容是可见的。早期浏览器默认行为是FOIT现在已经普遍支持font-display属性来控制。font-display: swap是最常用的策略它告诉浏览器“先别藏文字用备用字体显示等我下载完再换。”对中文网站来说swap几乎是必选项因为如果不加用户可能在慢网络下盯着白屏好几秒。后面讲具体操作时我会在CSS里把font-display一起给你写清楚。这一点如果你直接抄网上老代码很容易漏掉。3. 直接引入中文字体的完整实操流程3.1 获取字体文件与格式转换先讲直接引入毕竟它更通用。第一步是拿到字体文件。如果你有设计团队提供的正版字体授权文件直接用如果是个人项目我通常优先推荐思源黑体Source Han Sans和思源宋体Source Han Serif这两款是Adobe和Google联合开源的SIL Open Font License授权商用完全没问题。另外还有阿里巴巴普惠体、得意黑等都是开源且质量不错的国产中文字体。拿到TTF/OTF后我建议立刻转一份WOFF2。这里给出用fonttools转换的命令行方法# 安装fonttools如果已经装过可以跳过 pip install fonttools brotli # 将OTF/TTF转换为WOFF2 fonttools ttLib.woff2 compress SourceHanSansCN-Regular.otf -o SourceHanSansCN-Regular.woff2注意这里的brotli库是必须装的不然WOFF2压缩会报错。如果字体文件是OTF格式转出来没问题如果源文件本身就特别大比如超过10MB的完整字库你可以考虑在转换前先做一步“子集化”这个我放在后面性能优化部分专门讲。3.2 放置字体文件与font-face配置转换完成后在项目里建一个fonts目录把字体放进去。比如project/ ├── fonts/ │ ├── SourceHanSansCN-Regular.woff2 │ └── SourceHanSansCN-Regular.ttf ├── css/ │ └── style.css └── index.html然后在CSS里用font-face声明字体。这里有一个特别容易踩的坑src顺序不能乱。font-face { font-family: SourceHanSansCN; src: url(../fonts/SourceHanSansCN-Regular.woff2) format(woff2), url(../fonts/SourceHanSansCN-Regular.ttf) format(truetype); font-weight: 400; font-style: normal; font-display: swap; }为什么顺序重要因为浏览器会用“第一个它认识的格式”然后停止解析后面的。现代浏览器优先认WOFF2所以把WOFF2放第一个老浏览器不认WOFF2时才会继续看后面的TTF。如果你把TTF放前面那么所有浏览器都会下载TTFWOFF2的压缩优势就白白浪费了。光有font-face还不够还得把字体“用起来”。在body或者你想换字体的容器元素上设置font-familybody { font-family: SourceHanSansCN, -apple-system, PingFang SC, Microsoft YaHei, sans-serif; }这里注意font-family是优先级列表如果SourceHanSansCN加载失败浏览器会回退到系统自带的苹方、微软雅黑等。这个回退机制非常重要千万不能只写一个字体名不然字体出问题时整个页面会变成浏览器默认的丑字连系统字体都轮不上。3.3 多字重引入不要一股脑全上中文网站换字体时很多人会犯一个“礼貌性错误”把字体所有的字重都引入比如Regular、Medium、Bold、Light甚至Black然后到处用。结果就是用户打开你的网站要下载四五个字体文件每个2MB起步首屏数据直接爆炸。我的建议是普通内容站最多引两个字重Regular400和Bold700。如果设计稿里有Small标题不妨用Bold配合font-size和letter-spacing来弥补。如果你非要Light或者Medium那就只在你特别需要的地方用并且单独做子集化。多字重的font-face写法是这样的每个字重单独一个块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; }注意两个块必须用同一个font-family名字靠font-weight区分。如果你在HTML里写strong或者CSS里写font-weight: 700浏览器就会自动去匹配Bold那个文件。如果用不同的font-family名那跟直接换一种字体没什么两样还会增加CSS维护成本。4. 通过CDN引入中文字体的操作与选型指南4.1 公共字体CDN的使用方法CDN引入最典型的例子是Google Fonts。中文场景下Google Fonts收录了不少开源中文字体比如Noto Sans SC思源黑体的Google版、Noto Serif SC思源宋体。接入方式非常简单在HTML的head里加一行link标签link relpreconnect hrefhttps://fonts.googleapis.com link relpreconnect hrefhttps://fonts.gstatic.com crossorigin link hrefhttps://fonts.googleapis.com/css2?familyNotoSansSC:wght400;500;700displayswap relstylesheet然后CSS里直接用body { font-family: Noto Sans SC, PingFang SC, Microsoft YaHei, sans-serif; }这里有两个小细节。preconnect是告诉浏览器提前建立与该域名的连接能省下DNS解析和TCP握手的时间。第二行的crossorigin是必需的因为字体请求是跨域请求建立连接时就要带上跨域属性。不过请注意Google Fonts在国内的访问速度并不稳定。如果你面向的主要用户在国内直接挂Google Fonts几乎等于放弃了一部分用户的页面速度。我实测过部分地区访问Google Fonts域名会出现几秒超时字体一直不加载页面始终用回退字体渲染FOUT现象严重。相比之下国内有一些公共CDN镜像或者第三方字体托管平台比如某些开源镜像站、又拍云旗下的字体CDN服务。具体选择哪种我的建议是小流量个人项目优先找国内访问稳定的公共资源如果是商业项目或者流量上来之后尽快迁移到自托管省得受制于人。4.2 私有CDN分发从GitHub到对象存储如果你不想用公共字体库而是想把自己选好的字体文件通过CDN链路加载最轻量的做法是利用GitHub仓库加CDN加速。比如你把字体文件推到GitHub仓库里然后用jsDelivr的GitHub加速功能生成稳定链接。jsDelivr会在全球节点缓存文件国内访问速度总体还行但高峰期偶尔也会抽风。链接的通用格式是https://cdn.jsdelivr.net/gh/用户名/仓库名版本号/字体文件路径比如link relstylesheet hrefhttps://cdn.jsdelivr.net/gh/yourname/webfonts1.0/fonts.css或者直接用单个字体文件的URLfont-face { font-family: CustomFont; src: url(https://cdn.jsdelivr.net/gh/yourname/webfonts1.0/SourceHanSansCN-Regular.woff2) format(woff2); font-display: swap; }这种方式的优点是不需要自己买流量、不需要配置CDN只要维护好GitHub仓库就行。缺点是如果你在GitHub上修改文件但忘了更新版本号浏览器会因为缓存问题继续用旧字体调试成本高。如果你的项目已经有完整的云服务基础设施比如阿里云OSS、腾讯云COS或者七牛云那我更建议直接把字体传到对象存储里开启CDN加速域名。这样你能精确控制缓存策略、防盗链、访问日志字体文件和业务资源在一个体系内后续维护也统一。4.3 CDN引入的版本控制与跨域问题用CDN引字体有一个很容易被忽略的坑跨域。字体文件属于“需要CORS”的资源如果CDN服务器返回的响应头里没有Access-Control-Allow-Origin浏览器可能拒绝加载。Google Fonts和jsDelivr这两家都对字体文件做了完整的CORS支持所以直接用没问题。但如果你自己搭了个小CDN或者用某些对象存储的默认域名很有可能漏配CORS。排查跨域问题很简单打开浏览器的开发者工具切到Network面板刷页面后找到字体文件请求看它的HTTP状态码和响应头。如果状态码是200但浏览器控制台报跨域错误说明响应头里缺少CORS信息。国内对象存储的配置路径一般是在存储桶Bucket设置中找到“跨域设置”添加一条规则允许的来源*或你的域名允许的方法GET允许的响应头加上ETag等。配置完成后等一两分钟生效再刷新页面验证。另外CDN引入时建议在URL后面跟上版本参数或者把版本信息放在路径里比如1.0。这样当你更新字体文件时变更版本号就能强制用户获取新文件避免因为CDN缓存导致用户一直用旧字体。5. 常见问题与性能优化实录5.1 字体没生效的排查清单换了字体却一直不生效这是后台私信里问得最多的问题。我把排查路径整理成一张表遇到问题直接按这个顺序过一遍症状可能原因解决思路页面完全显示系统默认字体font-family名写错或CSS没被正确加载检查CSS选择器优先级在控制台查看计算样式打开页面时先看到默认字体过一会儿才变font-display未设置或设置了block导致FOUT/FOIT加上font-display: swap字体加载一半下半部分一直不换font-face块里只写了WOFF2老浏览器不认识补充TTF格式作为回退单页偶尔字体失效刷新又好了字体文件跨域被拦截检查CDN响应头CORS字体文件能下载但还是显示默认字体font-family里的字体名和font-face里的font-family不一致统一名称建议都用英文避免中文名兼容性问题只有部分文字换了字体字体文件本身是“子集化/精简版”缺某些字符换用完整字库或检查子集化是否包含所需字符范围5.2 性能优化中文字体子集化与按需加载如果你决定自托管中文字体性能优化的重点就是“别把整个字库都发给用户”。中文字体几千个常用字但一篇文章实际用到的字可能就几百个。子集化的思路就是把字体文件里你真正用到的那些字“抠”出来单独生成一个小文件。用fonttools做子集化相当方便# 生成只包含常用3500字的子集 pyftsubset SourceHanSansCN-Regular.otf \ --text的一是在不了有和人这中大为上个国我以要他时来用们生到作地于出就分对成会可主发年动同工也能下过子说产种面而方后多定行学法所民得经十三之进着等部度家电力里如水化高自二理起小物现实加量都两体制机当使点从业本去把性好应开它合还因由其些然前外天政四日那社义事平形相全表间样与关各重新线内数正心反你明看原又么利比或但质气第向道命此变条只没结解问意建月公无系军很情者最立代想已通并提直题党程展五果料象员革位入常文总次品式活设及管特件长求老头基资边流路级少图山统接知较将组见计别她手角期根论运农指几九区强放决西被干做必战先回则任取据处队南给色光门即保治北造百规热领七海口东导器压志世金增争济阶油思术极交受联什认六共权收证改清己美再采转更单风切打白教速花带安场身车例真务具万每目至达走积示议声报斗完类八离华名确才科张信马节话米整空元况今集温传土许步群广石记需段研界拉林律叫且究观越织装影算低持音众书布复容儿须际商非验连断深难近矿千周委素技备半办青省列习响约支般史感劳便团往酸历市克何除消构府称太准精值号率族维划选标写存候毛亲快效斯院查江型眼王按格养易置派层片始却专状育厂京东识述属构” \ --output-fileSourceHanSansCN-Regular.subset.woff2上面这个命令里--text后面的内容就是你要保留的字。实际项目中你可以先用爬虫抓取自己网站所有页面的正文文本去重后提取字表再生成对应的子集文件。这样生产环境字体文件可能从几MB缩到50KB左右加载速度肉眼可见地提升。但子集化也有代价如果你的博客有评论功能用户评论里可能包含你没收录的生僻字这些字就会回退到系统字体排版稍微不统一。折中方案是“主字体子集化 回退字体”正文用子集化字体评论区或者自由文本区域用完整字库字体。5.3 缓存、预加载与渲染体验调优字体文件属于静态资源缓存策略可以比HTML更激进。如果你是自托管建议在服务器或CDN上给字体文件设置Cache-Control: public, max-age31536000, immutable。这个一年的缓存时间看着夸张但字体文件本质上是带版本号的不可变资源只要URL不变内容就应该不变所以大胆缓存没问题。另外可以用link relpreload提前让浏览器知道字体文件的存在尤其是首屏就要用到的字体link relpreload href/fonts/SourceHanSansCN-Regular.woff2 asfont typefont/woff2 crossorigin注意preload字体请求必须带crossorigin属性即使字体文件和HTML同源也建议加上。这是因为字体请求默认走CORS模式不带这个属性可能导致预加载请求被忽略等于白写。我在一个内容站上实测过加上preload后字体开始下载的时间从600ms左右降到200ms以内首屏文字的字体替换延迟明显缩短。但建议只预加载首屏真正用得到的字体多个字体文件全部预加载反而是浪费带宽。5.4 版权与合规提醒最后必须提醒一个容易翻车的地方字体版权。中文字体是版权重灾区很多看起来“能下载”的字体其实只允许个人使用不允许商用。你在网上随便找一个“免费可商用中文字体包”下载里面可能混着未授权的商业字体一旦你的网站有广告收入、电商功能或者公司业务在用被字体厂商发律师函的案例并不少见。我常用的做法是优先选明确开源授权的字体思源系列、阿里巴巴普惠体、得意黑、站酷系列部分含限制条件。如果一定要用商业字体去官网确认价格和授权范围保留授权文件。在项目的README或者LICENSE文档里记录每个字体的授权来源方便后续审计。自托管字体文件时还要注意一个细节有些开源字体虽然允许商用但明确要求不得单独提取字体文件嵌入到其他软件或转售。如果你的网站允许用户下载字体文件最好在下载页加上授权说明避免替用户承担违规风险。6. 两条路怎么选我的建议做了这么多字体替换的项目我的体感是个人博客、内容站、临时项目优先CDN引入图个省事公司官网、SaaS产品、对用户体验有极致要求的项目直接引入自己能掌控的东西永远最靠谱。如果你在国内有大量用户CDN方案我倾向于使用国内公共资源库或国内容器镜像避开海外服务导致的加载波动。追求稳定的话直接引入加子集化是性能上限最高的方案。最后分享一个小技巧无论你最后选了哪种引入方式一定要在真实网络环境下用开发者工具的Network面板看一遍字体文件的加载时序。我经常看到有人本地一切正常部署后字体死活不生效多半就是跨域、缓存、路径这三个问题。按文章里给的方法排查基本10分钟内都能定位到原因。