前言
font-family 虽然只是一个 CSS 属性, 但是牵连许多东西, 所以独立一篇来讲.
网站一般上会使用 Google Fonts 作为 font-family, 下面会以一个 Google Fonts 为例子. 逐步解析它背后的 CSS 知识.
Pick a Google Font
到官网 fonts.google.com, 随便选一个 font 作为 font-family.
我选了 material 的 Roboto.
Select font-style and font-weight
知识点
1. 不是每个字体都有支持 Italic
2. 不是每个字体都支持 100 – 900 的 weight
3. 网站有使用到的才选择. 每一个 style 和 weight 都是一份字体文件, 下载越多越慢.
4. 常用的 weight 300, 400, 500, 700, Italic 尽量就别用了
作为示范, 我选了 normal 300, 400, 500, 700 和 Italic 400
右边点击 select, 就可以看见 selected 的 style 和 weight 了
Import CSS
它有 2 种 import 的 way.
第一是在 HTML 放 <link>
第二是在 CSS 放 @import, 我个人喜好是用这个
它其实就是一个去加载 CSS 的 URL
知识点
1. 以前是 css, 现在是 css2 版本不同了, Google 对字体做了一次改版, 如果你切换到 css 会拿到旧版本的字体
2. selected font-style
3. selcted font-weight
4. font-display 这个下面会详解讲它的作用.
如果有多个字体的话, 它的 URL 长这样
我加多了一个字体 Open Sans, 没有什么特别的. 只是 query param 多了 1 set family 而已 (相同 query param key 不常见, 但其实是符合规范的. 题外话: ASP.NET Core 获取 same query param key)
Set font-family Style
下方有声明如何使用在 Style
sans-serif 是 Roboto 的 fallback 字体.
效果
Import 的 CSS 做了什么?
上面有一个关键就是 Import CSS, 它做了啥?
用 DevTools 可以看到, 它加载了一个 CSS Style, 里面有 @font-face
@font-face
@font-face 是 CSS 语法. 用来 define 一个字体. 来看看它的属性.
@font-face { font-family: 'Roboto'; font-style: italic; font-weight: 400; font-display: swap; src: url(https://fonts.gstatic.com/s/roboto/v30/KFOkCnqEu92Fr1Mu51xFIzIFKw.woff2) format('woff2'); unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; }
font-family, style, weight 就不多说了.
src
字体文件 URL. 之前在 W3Schools 学习笔记 (3) – CSS Web Fonts 中有提过.
字体要渲染需要 TTF/OTF, WOFF 或者 WOFF2 文件.
所以, @font-face 只是定义了加载路径, 游览器会依据这个路径再去加载字体文件, 才能渲染.
另外, Google Fonts 是可以 self host 的哦, 通过 google-webfonts-helper 下载字体, 或者到官网下载 (but 官网的是 TTF, helper 是 WOFF2), 然后把 .woff2 或 .ttf 文件放到自己 server, src 改成 link to 自己 server 路径就可以了.
font-display
参考:
它有好几个值
auto: 依据游览器行为, 一般是 block
block: 上面说了游览器需要加载 .woff 文件, 这段期间字体要如何展现呢?
block 就是不显示. 直接给一个白色, 完全看不到字, 直到字体下载完成渲染后才显示. 这个体验叫 FOIT (Flash Of Invisible Text), 很少人这样做的, 体验不好
swap: 在等待加载的时候, 先显示 fallback 字体, 等加载完成才显示最终字体. 这个体验叫 FOUT (Flash Of Unstyled Text), 大部分人都这样做 (虽然体验也不是 100% 的好)
fallback: block 和 swap 的折中方案, 会先 block 100ms 左右, 然后 3秒 swap, 如果 3 秒后还没有加载成功, 那么就一直用 fallback 的. 考虑到 3 秒后用户已经开始阅读内容了, 这时切换字体会很明显跳一下, 体验扣很多分
optional: 和 fallback 类似. 只是把权力交给游览器依据下载速度决定是否去加载字体.
block, swap, fallback, optional 可以理解为你多在意自定义字体. block 就是一定要用自定义字体, 其它的连看都不可以. swap 就是可以先看 fallback 但最后一定要看到自定义
fallback 就不一定要看到自定义, 加载快才看. optional 顾名思义, 更加不注重了. 大部分人都会用 swap.
unicode-range 没有认真研究. 从 Google Fonts 的 @font-face 可以看到, 同一个 family, style, weight 会有很多版本的 unicode-range.
提醒:
不同 font-family, 不同 style, 不同 weight, 不同 unicode 都需要新的一个 @font-face 哦. 所以 @font-face 会有很多很多的.
CSS Font Loading API
参考:
stackoverflow – How to be notified once a web font has loaded
Medium – Getting started with CSS Font Loading
上面说到, @font-face 定义了 .woff2 的加载路径, 然后游览器去加载文件后渲染.
font-display swap 会先显示 fallback 的字体, 然后切换, 所以字体的 size 会改变.
如果我们依赖这个字体的 width 怎么办呢? 虽然可以用 ResizeObserver 监听, 但它不是最好的. 更好的是用 document.fonts
document.fonts 是一个 iterable 对象. 它记入了所以 @font-face 的内容.
for (const font of document.fonts) { console.log("font", font); }
遍历后可以看到所以 @font-face 定义的字体
下面 2 个监听方式
document.fonts.ready.then(() => { console.log("all loaded"); }); [...document.fonts][0].loaded.then(() => { console.log("specify font loaded"); });
第一个是监听所以 font 加载完毕
第二个是指定某一个 font 加载完毕
JS 动态加载 font-face
上面都是靠 CSS @font-face 去加载的. JavaScript 也是有 API 可以动态加载 font 的哦.
注释 CSS
/* @font-face { font-family: "Roboto"; font-style: normal; font-weight: 400; font-display: swap; src: url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxK.woff2) format("woff2"); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } */
JavaScript
document.querySelector("button")!.addEventListener("click", () => { // define const robotoFontFace = new FontFace( "Roboto", `url(https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxK.woff2) format("woff2")`, { display: "swap", style: "normal", weight: "400", unicodeRange: `U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD`, } ); // load robotoFontFace.load().then(() => { // add to document.fonts document.fonts.add(robotoFontFace); // set style document.querySelector("h1")!.style.fontFamily = `Roboto`; }); });
new FontFace 类似 CSS 的 @font-face. 照搬而已.
定义好 FontFace 把它添加到 document.fonts 里面去. 然后就可以使用了.
我上面调用 load 只是一个示范而已. 其实只要添加进了 document.fonts 它就会自动去加载了.
和 font-family 有关的文章
webpack – postcss-font-magician
总结
1. font-family 需要 .woff2 文件 (.woff2 文件就是 font 的设计文档, 有些是版权的哦, 不可以随便用)
2. CSS Style @font-face 只定义了加载路径
3. 游览器加载 font 时, font-display 控制体验
4. JS 也有 API 可以设置 font-face 和监听 font rendered.