前端字体那些事儿
字体对于前端开发来说应该是比较熟悉又陌生的概念,熟悉是因为会经常使用 css 来设置字体,陌生是因为设计经常会告诉你怎么实现的页面看起来和设计稿字体不一样,有时候自己没搞清楚也不知道该怎么和设计解释,那这次就相对全面地来了解一下前端字体的那些事儿。
一、使用
字体的使用大家应该都很熟悉,这里不再展开讲,简单来说就是设置一串字体字符串,不同字体用逗号隔开,浏览器会根据字体安装情况从前往后选择使用。更多信息可以参考 MDN 上面的 font-family相关文档。
二、检测字体安装
大部分人可能对字体的使用就停留在上面那一步了,因为更细节的属于浏览器设置系统范畴了,我们是没有办法控制的,但是在一些特别的产品例如文档,对于字体的使用就需要更加深入的研究了,第一个可能遇到的问题就是检测本地安装了哪些字体。
先说结论:前端没有办法检测本地系统安装了哪些字体,但是可以知道是否安装了某个字体,所以使用穷举法可以间接知道安装了哪些字体。
2.1 字体名称
想要使用一个字体,首先要知道这个字体叫什么名字,大部分已经存在的字体网上可以找到相关资料,但是如果是一些特有字体,例如阿里推出的阿里巴巴普惠体,那该如何知道它在系统中的名称是什么呢?这里建议使用 safari 的提示功能。
从上图可以看到,在设置 font-family 属性的时候,safari 是有字体提示的,所以用这个提示可以间接知道在系统中这个字体名称是什么。
2.2 特定字体检测
知道了字体名称,接下来就可以进行检测了,检测比较容易想到的还是使用:
{
font-family: "检测字体";
}
但是这里有个问题,从代码层面并不知道系统是否使用了这个特定字体,那这里的解决方案相对来说比较 hack,那就是使用自定义安装字体。
前端如何安装字体后面会有详细介绍,假设现在已经安装了一个字体 TestFont,但是这个字体比较特殊,它可以只有一个字符,比如常见的 0,但是宽度是 0,那接下来使用下面的写法:
{
font-family: "检测字体, TestFont";
}
利用浏览器的字体回退能力,如果使用了我们需要检测的字体,那正常情况下宽度不会是 0,如果检测的字体没有安装,那就会使用 TesFont,宽度就会是 0,通过这种方式就可以知道字体是否安装。但是理论上也有一些缺陷,为了快速检测,一般这种 TestFont 体积不会很大,里面可能就包含一个字符,那如果待检测字体没有这个字符,那就会检测不到。考虑到绝大部分字体都有阿拉伯数字,所以使用字符 0 相对比较安全。
至于如何检测宽度,这个有两种方法:
方法一:使用 dom 的 getBoundingClientRect
方法二:使用 canvas 的 measureText
这里就不展示相关代码了,大家应该都比较熟悉了。
2.3 特殊情况
这里主要有两种场景需要注意:
- safari 因为安全原因,是不支持本地用户安装的字体的
stackoverflow.com/questions/5…
所以在 safari 下,安装的字体是检测不到的,目前也没有解法
- 字体设置时需要注意引号
字体规范里面规定字体的名称是有要求的,如果有空格、小数点、数字这些是建议用引号的
所以
{
font-family: Gill Sans Extrabold, sans-serif;
}
这种写法合法但是不推荐,但是这里有个坑,如果字体名称里面有小数点,那这种写法就是不合法的,不会生效,例如下面这个:
// 不合法
{
font-family: Gill Sans Extrabold1.0, sans-serif;
}
// 合法
{
font-family: "Gill Sans Extrabold1.0", sans-serif;
}
三、安装网络字体
上面的检测方法除了能检测本地字体是否安装外,也可以检测是否有安装相关网络字体,或者说它不能区分字体是安装在本地系统还是通过网络安装的。无论如何,有了这个能力以后,就可以根据业务需求进行网络字体下载。
3.1 @font-face
这一部分内容在MDN 上已经有比较详细的教程了,这里就不赘叙了。
目前支持的字体格式有 TTF、OTF、WOFF、WOFF2,以前的写法为了兼容性,所以会写所有格式的字体,但是其实现在 WOFF2 的兼容性已经比较好了,所以业务可以根据自己的兼容需求来决定要不要简化字体设置。
3.2 document.fonts
这个大部分人应该不是很熟悉,是用的相对比较少的接口,返回的是 FontFaceSet,有 add、check、clear、delete 等等接口。
下面是 MDN 上面的添加字体的 demo:
const font = new FontFace("MyFont", "url(myFont.woff2)");
font.load().then(
() => {
document.fonts.add(font);
},
(err) => {
console.error(err);
}
);
可以看到,FontFaceSet 里面存储的是 FontFace,FontFace 的第二个参数可以是一个字体的下载地址,也可以是 ArrayBuffer。
整体来说,document.fonts 的接口目前兼容性也还是不错的。
3.3 特殊情况
前面提到过,如果字体里面有小数点这种情况,那安装的时候需要用什么名称呢?经过测试,安装字体直接使用原始字符串就行,否则会不生效:
// 设置
ele.style.fontFamily = '"Gill Sans Extrabold1.0"';
// 安装
const font = new FontFace("Gill Sans Extrabold1.0", "url(myFont.woff2)");
font.load().then(
() => {
document.fonts.add(font);
},
(err) => {
console.error(err);
}
);
四、验证字体
安装网络字体后,就可以使用 font-family 来看是否生效,很多人可能是通过肉眼来区分,但是这个可能不准或者比较难,这里可以借助浏览器的能力来验证。
4.1 chrome
chrome 的 style 的 computed 面板里面,最下面一行会有使用的字体信息,通过这个可以知道当前浏览器用的是哪个字体
4.2 safari
safari 的调试面板里面自带了字体这个 tab,但是这个似乎不准,例如在 mac 下,同样设置 Arial 字体,safari 显示 Arial,chrome 显示的是平方,当然也有可能是浏览器在字体选择上有差异,这里没有深究。
五、未来
可以看到,目前做的这些工作,很多都是因为浏览器不允许前端直接访问本地字体,但是从 Chrome 103 版本开始,支持前端通过 window.queryLocalFonts 获取安装在本地的字体列表甚至访问字体数据:
下面是 demo:
async function logFontData() {
try {
const availableFonts = await window.queryLocalFonts();
for (const fontData of availableFonts) {
console.log(fontData.postscriptName);
console.log(fontData.fullName);
console.log(fontData.family);
console.log(fontData.style);
}
} catch (err) {
console.error(err.name, err.message);
}
}
所以在可以预见的未来,前端对本地字体将会有更强的掌控力,可能也会因此诞生更多有趣的创意以及应用,让我们拭目以待!