4、@font-face 规则:从声明到精准控制
好,咱们进入 @font-face 的世界。这玩意儿是自定义字体的基石,说白了就是告诉浏览器:「嘿,我这里有个字体文件,你按我给的规则去加载和使用。」
我在项目里见过太多人直接把 @font-face 当模板复制粘贴,结果出了问题一脸懵。今天咱们把它拆开揉碎了讲清楚。
4.1 @font-face 语法详解
先看一个最完整的声明结构:
@font-face {
font-family: 'MyCustomFont';
src: url('fonts/myfont.woff2') format('woff2'),
url('fonts/myfont.woff') format('woff'),
local('My Custom Font'),
local('MyCustomFont-Regular');
font-weight: 400;
font-style: normal;
font-display: swap;
unicode-range: U+0025-00FF;
}
每个属性都有它的使命。我习惯把 @font-face 想象成一份「字体简历」——告诉浏览器这个字体的名字、它在哪、什么情况下能用。
| 属性 | 作用 | 是否必填 |
|---|---|---|
font-family |
定义字体的引用名称 | 是 |
src |
指定字体文件来源 | 是 |
font-weight |
声明该字体的字重 | 推荐 |
font-style |
声明该字体的样式 | 推荐 |
font-display |
控制字体加载时的渲染行为 | 推荐 |
unicode-range |
限定字体覆盖的字符范围 | 可选 |
font-family 的名字可以随便起,但必须和后续 CSS 中引用的名字完全一致。我曾经见过有人声明时叫 'PingFang SC',引用时写成 'PingFangSC',结果字体死活不生效。
4.2 src 属性与 local() 函数
src 是 @font-face 里最核心的属性。它告诉浏览器去哪里找字体文件。这里有个优先级问题——浏览器会按你写的顺序依次尝试。
4.2.1 url() 与 format()
url() 指向远程字体文件,format() 提示浏览器文件格式。我建议永远把 woff2 放在最前面:
src: url('fonts/roboto.woff2') format('woff2'),
url('fonts/roboto.woff') format('woff');
为什么?因为 woff2 压缩率最高,体积最小。浏览器如果支持 woff2,就直接下载它,跳过后面的 woff。不支持?那就继续尝试 woff。这叫「渐进增强」。
4.2.2 local() 函数
local() 是个容易被忽略但很有用的东西。它检查用户本地是否已经安装了某个字体。如果装了,就直接用本地的,省去一次网络请求。
src: local('Roboto Regular'),
local('Roboto-Regular'),
url('fonts/roboto.woff2') format('woff2');
嗯,这里要注意:local() 里的名字必须和系统里注册的字体名完全一致。Windows 和 macOS 的字体命名规则还不一样。我踩过这个坑——在 Mac 上写 local('PingFang SC') 没问题,但 Windows 上可能得写成 local('Microsoft YaHei')。
local(),再写 url()。这样用户本地有字体就直接用,没有才去下载。对于中文字体这种动不动几 MB 的大家伙,这个优化特别明显。
4.3 font-display 属性
这个属性控制的是「字体还没加载完时,页面怎么显示文字」。说白了就是解决 FOIT(Flash of Invisible Text)和 FOUT(Flash of Unstyled Text)的问题。
| 值 | 行为 | 适用场景 |
|---|---|---|
auto |
浏览器默认行为(通常等同于 block) | 不推荐,交给浏览器赌运气 |
block |
先隐藏文字,等字体加载完再显示 | 图标字体、品牌字体(必须精确展示) |
swap |
先用后备字体显示,字体加载完再替换 | 正文内容、博客文章(优先可读性) |
fallback |
短时间隐藏,加载不完就用后备字体 | 对字体有要求但可妥协的场景 |
optional |
极短时间隐藏,加载不完就放弃 | 弱网环境、装饰性字体 |
我个人最常用 swap。你想想看,用户打开页面,文字先显示出来让他能读,等字体加载完了再优雅替换——这才是用户体验优先的做法。
避坑指南: 我曾经在一个电商项目里用了 block,结果用户网络差的时候,整个商品标题区域空白了 3 秒。转化率直接掉了 5%。从那以后,正文内容我坚决用 swap。
4.4 unicode-range 应用
unicode-range 是个高级玩法。它让你精确控制某个 @font-face 规则只对特定字符生效。
/* 只对英文和数字使用 Roboto */
@font-face {
font-family: 'MyFont';
src: url('roboto.woff2') format('woff2');
unicode-range: U+0020-007E;
}
/* 只对中文使用 Noto Sans SC */
@font-face {
font-family: 'MyFont';
src: url('notosanssc.woff2') format('woff2');
unicode-range: U+4E00-9FFF;
}
这样做的好处很明显:英文用西文字体,中文用中文字体,各自只加载需要的字符集。中文字体文件通常很大,如果只加载几千个常用汉字,体积能小很多。
常见的 Unicode 范围:
| 范围 | 含义 |
|---|---|
U+0020-007E |
基本拉丁字母(英文、数字、标点) |
U+4E00-9FFF |
CJK 统一表意文字(常用汉字) |
U+FF00-FFEF |
全角字符(中文标点、全角字母) |
U+3000-303F |
CJK 符号和标点 |
unicode-range 时,多个 @font-face 的 font-family 必须相同,否则浏览器会当成不同字体处理。我见过有人把两个规则写成不同名字,结果一个都没生效。
下面这张图帮你理清 @font-face 的核心逻辑:
总结一下:@font-face 的核心就是「声明—来源—策略—范围」四个维度。声明字体名字,告诉浏览器去哪找,决定加载时的表现,限定覆盖的字符。把这四点吃透了,自定义字体这块你就拿捏住了。