Web技巧(12)

1,122 阅读30分钟

这一期中我们将围绕着Web中的font来展开。在现代Web中除了能使用font-family属性给Web应用指定字体之外,还有其他一些用于字体的特性,比如@font-face可以加载非系统的字体,字体变体属性font-variation-*让Web上排版和印刷上排版之间的差距在逐渐拉小,font-display属性来决定非系统方面字体的加载策略,提高性性能,font-palette用来选择字体配色,@font-palette-values自定义字体配色等。如果你感兴趣的话,请继续往下阅读。

@font-face的使用

现代Web的开发,除了追求技术上的完善之外,有些开发者是设计出身,很多时候也追求艺术上的更好展现。特别是对于Web字体来说,受限于系统可用的字体非常的有限。但随着@font-face的出现,Web开发者可以使用第三方(非系统内置)的字体,比如:

@font-face {
    font-family: 'NeuesBauenDemo';
    src: url('../fonts/neues_bauen_demo-webfont.eot');
    src: url('../fonts/neues_bauen_demo-webfont.eot?#iefix') format('embedded-opentype'),
    url('../fonts/neues_bauen_demo-webfont.woff') format('woff'),
    url('../fonts/neues_bauen_demo-webfont.ttf') format('truetype'),
    url('../fonts/neues_bauen_demo-webfont.svg#NeuesBauenDemo') format('svg');
    font-weight: normal;
    font-style: normal;
}

.neuesDemo {
    font-family: 'NeuesBauenDemo'
}

比如上面的示例,可以让Web上的字体更具艺术范:

@font-face除了能让Web开发者使用第三方字体之外,还有另外一个优势,而且在Web中运用也非常的常见,即字体图标。比如Font Awesome就是一个非常受欢迎的用字体制作的图标库。

@font-face {
    font-family: 'FontAwesome';
    src: url('font/fontawesome-webfont.eot');
    src: url('font/fontawesome-webfont.eot?#iefix') format('embedded-opentype'), url('../font/fontawesome-webfont.woff') format('woff'), url('../font/fontawesome-webfont.ttf') format('truetype'), url('../font/fontawesome-webfont.svgz#FontAwesomeRegular') format('svg'), url('../font/fontawesome-webfont.svg#FontAwesomeRegular') format('svg');
    font-weight: normal;
    font-style: normal;
}

<div class="icon-glass"></div>

使用也非常的方便。而且在现使用Font Awesome只需要调用相关的资源链接,需使用的时候指定相应的图标类名即可:

上面看到的字体都是现成的,如果你是一名设计师或者说你懂得字体的设计。那么你就可以在任何Web页面或Web应用上使用你自己设计的字体。这样一来,是不是非常有成就感。同样的原理,还可以设计一些SVG矢量图标,借助IcoMoon Web App将图标生成Web可用的Web字体:

如果你对Web中字体图标相关的东西感兴趣的话,还可以阅读下面相关文章:

@font-face带来很多优势,但大多人一般只会聊自定义的Web字体如何定义,但是没有过多的人考虑其实际性能:

特别是网络环境不好或弱网情况之下,可能访问带有Web字体的应用会对用户带来一些不好的体验。比如下面这个录展所示的效果:

基本的@font-face使用方法会至使用户加载字体受到阻塞。不过庆幸的是,我们可以找到一些技术方案来改善字体加载性能,让使用Webp字体的性能更好,加载字体更流畅。后面我们会聊到这些相关的技术。

如何使用第三方字体

你或许经常会看到有网站像下面这样的引用非系统的字体:

<!-- HTML中通过link标签引用 -->
<link href="https://fonts.googleapis.com/css?family=Gloria+Hallelujah" rel="stylesheet">

或者:

/* 在CSS中通过@import引用 */
@import url('//fonts.googleapis.com/css?family=Lato:400,400italic,700|Sansita+One');

打开字体文件,你会发现代码如下:

/* latin-ext */
@font-face {
    font-family: 'Lato';
    font-style: italic;
    font-weight: 400;
    src: local('Lato Italic'), local('Lato-Italic'), url(https://fonts.gstatic.com/s/lato/v15/S6u8w4BMUTPHjxsAUi-qNiXg7eU0.woff2) format('woff2');
    unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* 部分代码略去 */

可以看到,依旧离不开@font-face这个属性。当然里面还有一些其他的设置,比如unicode-range

简单地说,上面的示例使用了Google Font,而且使用Google Font的人非常的多。在Google Fonts上有很多字体能提供大家选用:

其实按这样的原理,我们也可以把自己的图标提交到线上,使用CDN地址。

使用Google字体还是有一些小技巧,这些技巧有助于我们搞高字体加载的性能。后面会聊一些这些技巧。如果你的字体也放在CDN上,也可以按类似的方案来做处理。如果感兴趣话,敬请后面的内容。

Web中的中文字体

这里所说的Web中的中文字体,并不是我们系统常见的字体,比如“宋体”、“楷体”、”萍方“和”微软雅黑“等。拿个示例来说吧,在UI还原中,你肯定碰到带有艺术字体的设计稿:

碰到这样的场景大部分解决方案,要么是使用系统默认字体,要么是使用图片来替换。或许你会问:

既然@font-face这么牛逼,为什么中文艺术字体不用该特性呢?

原因非常简单,对于英文而言,它只有26字母,一张ASCII码上128个字符集,体量较小,设计成本较小。但对于中文而言,单单GB2313编码的中文字字符就达到7445个,体量较大,设计成本较大。这就是中文相比于英文不太好设计的原因之一,如果要设计出这么一套中文字体(带艺术性,个性化字体),除了设计成本和难度较大之外,就算是设计出来,字体体积也非常的大,面对这么大的字体文件,要是运用于Web上,也不是件易事。最起码要面对字体的加载,性能的优化等问题。

不过并不是没有任何方案可解,在社区中也有相应的字体裁剪工具,比如:

就算是有了这些工具,我们也无法大面积的在Web上使用特殊的中文字体。而在中文Web应用程序中,使用这种艺术字体的场景也不是全站都是,大部分会出现在标题、Banner等场景。而这样的场景下所需的字体数量有限的,这样我们就可以借助上面的两个工具对字体进行裁剪,生成一个只包含特定字符的小字体文件。这样就达到了减少字体文件的目标。

有关于这方面的详细介绍可以阅读下面几篇文章:

字体加载性能优化

前面提到过,使用@font-face使用第三方字体的时候受限于字体的大小,网络的影响等,对用户的体验是会有相应的影响。除了降低字体包的大小之外还有一些别的优化方式。比如@Danny Cooper在他的最新博客中就聊到了如何优化Google Fonts性能。在这篇文章中提到的一些优化方案其实都适合使用@font-face的任何地方。

下面简单的来了解一下@Danny Cooper在文章中给我们介绍哪些技术手段能对字体方面做相关的优化。

不管是使用的Google Fonts还是其他地方提供的字体。可以说每种字体在Web浏览器中显示之前都需要先下载。通过正确的设置,额外的加载时间并不明显。但是,如果出现错误,用户可能需要等待一定的时候才能显示。接下来,作者拿Google Fonts为例,阐述了如何对字体做相关的性能优化。

Google Fonts已经做过相应的优化

Google Fonts API不仅仅向我们提供字体文件,它还有一个更大的优势,就是会执行智能检查,以查看如何以最优化的格式交付文件。 比如Roboto字体,Github告诉我们,常规版的字体体积大约为168kb

如果我们从API请求相同的字体变体(Font variant)就会得到这个文件,只有11kb。是不是很神奇。那是因为,当浏览器向API发出相应的请求时,Google首先会检查浏览器支持哪些文件类型。比如最新版本的Chrome浏览器和大多数的浏览器一样,也支持WOFF2,所以字体是以高度压缩的格式提供给我们的。

不同的浏览器会获取不同的字体格式。

最为强大的是,Google Fonts 为 每种字体维护了30多个优化的字体变体,并自动检查和交付每种平台和浏览器的最佳变体。@Ilya Grigorik在这方面做过深入的讨论,详细的可以阅读《网页字体优化》一文。

网页字体是一个字形集合,而每个字形是描述字母或符号的矢量形状。 因此,特定字体文件的大小由两个简单变量决定:每个字形矢量路径的复杂程度和特定字体中字形的数量

浏览器缓存

Google Fonts的另一个内置优化是浏览器缓存

由于Google Fonts的普遍性,浏览器并不总是需要下载完整的安体文件。例如,使用了Mija这样的一个字体,如果你的浏览器首次看到该字体,在需要显示之前会下载该字体,但下次再重新访问该网站时使用之个字体,浏览器将会使用缓存版本。

Google Fonts浏览器缓存被设置为一年后过期,除非缓存被提前清除。

进一步优化的可能

虽然谷歌在优化字体文件的交付方面投入了大量的精力,但是仍然可以在实际使用中进行一些更多的优化,以减少对页面加载时间的影响。

字体库做相应的限制

最简单的优化注是使用更少的字体库。每种字体的页面重量加起来可能达到400kb,再乘以几个不同的字体族,您的字体的重量就会突然超过整个页面的重量。@Danny Cooper在文章中建议:

在页面中不要使用超过两种字体,一种用于标题,另一种用于内容。

使用正确的字体大小、重量和颜色,即使只有一种字体,在性能上也有较好的优化。

排队变体

Google Fonts质量是出了名的,而Google为了让字体具有较高的质量标准,请多字体都包含了可用的字体权重(font-weight):

权重关键词 权重对应的值 权重关键词 权重对应的值 权重关键词 权重对应的值 权重关键词 权重对应的值
Thin 100 Thin Italic 100i Light 300 Light Italic 300i
Regular 400 Regular Italic 400i Medium 600 Medium Italic 600i
Bold 700 Bold Italic 700i Black 800 Black Italic 800i

这对于可能需要所有12种变体的高级用例来说是非常好的,但是对于一个普通的网站,意味着下载所有12种变体,就过于浪费,因为你可能只会用到34种变体。例如,Roboto字体系列的重量大约为144kb。但是,如果你只使用常规的,常规的斜体和粗体等,那么这个字体的重量就可以下降到大约36kb,相当于节约了75%

一般情况下,加载Google Fonts的默认代码如下:

<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">

这样做,只是加载了常规400Regular 400)版本。这意味着字体集中的其他版本,比如LightBoldItalic将不会正确的显示。如果要改变这个现象,即 要加载所有字体变体,可以在href中的URL显式指定字体变体的权重,如下所示:

<link href="https://fonts.googleapis.com/css?family=Roboto:100,100i,300,300i,400,400i,500,500i,700,700i,900,900i" rel="stylesheet">

前面也提到过了,很少网站会使用所有字体的变体,从100900,而实际使用中,最佳的方式是指定你可能需要的字体变体权重,比如:

<link href="https://fonts.googleapis.com/css?family=Roboto:400,400i,600" rel="stylesheet">

这种方式,在使用多个字体的时候更显得重要。比如,如果使用Lato作为Web中的标题,它可能只会请求粗体(可能还会有粗体斜体):

<link href="https://fonts.googleapis.com/css?family=Lato:700,700i" rel="stylesheet">

减少http请求

不管是使用Google Fonts(如果没有下载到本地)或者使用其他CDN上的线上字体库,都会需要相应的HTTP请求。对于Web应用而言,发出的HTTP请求越多,加载所需的时间就越长,对于性能的影响就越大。如果你的页面中使用到多个字体时,就要面对HTTP请求增多的事实。实际上,我们可以按下面这种方式对请求做相应的优化,可以减少HTTP的请求数:

<!-- 优化前的方式 -->
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,400i,600" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">

<!-- 优化后的方式 -->
<link href="https://fonts.googleapis.com/css?family=Roboto|Open+Sans:400,400i,600" rel="stylesheet">

资源提示

资源提示(Resource Hints)是现代浏览器支持的一种特性,可以用来提高网站的性能。该特性包含了prefetchpreloadpreconnectdns-prefetchprerender等。它们可以用于<link>标签的rel中,告诉浏览器预加载一些东西:

<link rel="prefetch" href="/style.css" as="style" />
<link rel="preload" href="/style.css" as="style" />

<link rel="preconnect" href="https://example.com" />
<link rel="dns-prefetch" href="https://example.com" />

<link rel="prerender" href="https://example.com/about.html" />

其中dns-prefetchpreconnect对于字体加载方面的优化有很大的作用。

dns-prefetch允许浏览器在页面开始加载时立即启动和Google Fonts API(fonts.googleapis.com)的连接。这意味着当浏览器准备发出请求时,一些工作已经完成了。要实现谷歌字体的dns-prefetch,只需要在<head>标签中添加像下面这样的一行代码即可:

<link rel="dns-prefetch" href="//fonts.googleapis.com">

另外,谷歌字体的嵌入代码看上去好像只发出一个单一的HTTP请求:

<link href="https://fonts.googleapis.com/css?family=Roboto:400,400i,700" rel="stylesheet">

事实上并非如此如果我们访问https://fonts.googleapis.com这个URL,它并不只发出一个HTTP请求,其实他实际上发出了多个请求。

/* cyrillic-ext */
@font-face {
    font-family: 'Roboto';
    font-style: italic;
    font-weight: 400;
    src: local('Roboto Italic'), local('Roboto-Italic'), url(https://fonts.gstatic.com/s/roboto/v19/KFOkCnqEu92Fr1Mu51xFIzIXKMnyrYk.woff2) format('woff2');
    unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
    font-family: 'Roboto';
    font-style: italic;
    font-weight: 400;
    src: local('Roboto Italic'), local('Roboto-Italic'), url(https://fonts.gstatic.com/s/roboto/v19/KFOkCnqEu92Fr1Mu51xMIzIXKMnyrYk.woff2) format('woff2');
    unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
    font-family: 'Roboto';
    font-style: italic;
    font-weight: 400;
    src: local('Roboto Italic'), local('Roboto-Italic'), url(https://fonts.gstatic.com/s/roboto/v19/KFOkCnqEu92Fr1Mu51xEIzIXKMnyrYk.woff2) format('woff2');
    unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
    font-family: 'Roboto';
    font-style: italic;
    font-weight: 400;
    src: local('Roboto Italic'), local('Roboto-Italic'), url(https://fonts.gstatic.com/s/roboto/v19/KFOkCnqEu92Fr1Mu51xLIzIXKMnyrYk.woff2) format('woff2');
    unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
    font-family: 'Roboto';
    font-style: italic;
    font-weight: 400;
    src: local('Roboto Italic'), local('Roboto-Italic'), url(https://fonts.gstatic.com/s/roboto/v19/KFOkCnqEu92Fr1Mu51xHIzIXKMnyrYk.woff2) format('woff2');
    unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
    font-family: 'Roboto';
    font-style: italic;
    font-weight: 400;
    src: local('Roboto Italic'), local('Roboto-Italic'), url(https://fonts.gstatic.com/s/roboto/v19/KFOkCnqEu92Fr1Mu51xGIzIXKMnyrYk.woff2) format('woff2');
    unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
    font-family: 'Roboto';
    font-style: italic;
    font-weight: 400;
    src: local('Roboto Italic'), local('Roboto-Italic'), url(https://fonts.gstatic.com/s/roboto/v19/KFOkCnqEu92Fr1Mu51xIIzIXKMny.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;
}
/* cyrillic-ext */
@font-face {
    font-family: 'Roboto';
    font-style: normal;
    font-weight: 400;
    src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v19/KFOmCnqEu92Fr1Mu72xKKTU1Kvnz.woff2) format('woff2');
    unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
    font-family: 'Roboto';
    font-style: normal;
    font-weight: 400;
    src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v19/KFOmCnqEu92Fr1Mu5mxKKTU1Kvnz.woff2) format('woff2');
    unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
    font-family: 'Roboto';
    font-style: normal;
    font-weight: 400;
    src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v19/KFOmCnqEu92Fr1Mu7mxKKTU1Kvnz.woff2) format('woff2');
    unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
    font-family: 'Roboto';
    font-style: normal;
    font-weight: 400;
    src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v19/KFOmCnqEu92Fr1Mu4WxKKTU1Kvnz.woff2) format('woff2');
    unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
    font-family: 'Roboto';
    font-style: normal;
    font-weight: 400;
    src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v19/KFOmCnqEu92Fr1Mu7WxKKTU1Kvnz.woff2) format('woff2');
    unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
    font-family: 'Roboto';
    font-style: normal;
    font-weight: 400;
    src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v19/KFOmCnqEu92Fr1Mu7GxKKTU1Kvnz.woff2) format('woff2');
    unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
    font-family: 'Roboto';
    font-style: normal;
    font-weight: 400;
    src: local('Roboto'), local('Roboto-Regular'), url(https://fonts.gstatic.com/s/roboto/v19/KFOmCnqEu92Fr1Mu4mxKKTU1Kg.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;
}
/* cyrillic-ext */
@font-face {
    font-family: 'Roboto';
    font-style: normal;
    font-weight: 700;
    src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v19/KFOlCnqEu92Fr1MmWUlfCRc4AMP6lbBP.woff2) format('woff2');
    unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
    font-family: 'Roboto';
    font-style: normal;
    font-weight: 700;
    src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v19/KFOlCnqEu92Fr1MmWUlfABc4AMP6lbBP.woff2) format('woff2');
    unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
    font-family: 'Roboto';
    font-style: normal;
    font-weight: 700;
    src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v19/KFOlCnqEu92Fr1MmWUlfCBc4AMP6lbBP.woff2) format('woff2');
    unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
    font-family: 'Roboto';
    font-style: normal;
    font-weight: 700;
    src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v19/KFOlCnqEu92Fr1MmWUlfBxc4AMP6lbBP.woff2) format('woff2');
    unicode-range: U+0370-03FF;
}
/* vietnamese */
@font-face {
    font-family: 'Roboto';
    font-style: normal;
    font-weight: 700;
    src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v19/KFOlCnqEu92Fr1MmWUlfCxc4AMP6lbBP.woff2) format('woff2');
    unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
    font-family: 'Roboto';
    font-style: normal;
    font-weight: 700;
    src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v19/KFOlCnqEu92Fr1MmWUlfChc4AMP6lbBP.woff2) format('woff2');
    unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
    font-family: 'Roboto';
    font-style: normal;
    font-weight: 700;
    src: local('Roboto Bold'), local('Roboto-Bold'), url(https://fonts.gstatic.com/s/roboto/v19/KFOlCnqEu92Fr1MmWUlfBBc4AMP6lQ.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;
}

如果在<link>标签中指定rel="preconnect"时,这些附加的请求的问题是,直到第一个对https://fonts.googleapis.com/css的请求完成之后,浏览器才会开始处理这些请求。

也可以说preconnectprefetch的增强版。它是在浏览器将要加载的特定URL上设置它。它不仅执行DNS查找,还完成了TSL协商和TCP握手。

<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin>

仅仅添加这一行代码就可以将页面加载时间减少100ms

如果你对Resource Hints相关的技术感兴趣的话,还可以阅读下面相关的教程进行扩展:

本地字体

谷歌字全是在”Libre“或”自由软件“许可下授权的,它允许你在没有请求许可的情况下自由使用、更改和分发字体。言外之意,如果你不想使用谷歌的主机,可以自己托管。甚至可以将字体下载到自己本地,做一些其他的优化。

另外,谷歌字体还提供一个服务,允许你选择要使用的字体,然后提供所需的文件和CSS。

font-display

font-display属性提供了几个不同的属性值,对于字体的加载可以做相应的优化。font-display常常和@font-face配合在一起使用:

@font-face {
    font-family: 'Roboto';
    src: local('Roboto Thin Italic'),
url(https://fonts.gstatic.com/s/roboto/v19/KFOiCnqEu92Fr1Mu51QrEz0dL-vwnYh2eg.woff2)
format('woff2');
    font-display: swap;
}

如果引用谷歌在线的字体,还可以这样使用:

<link href="https://fonts.googleapis.com/css?family=Noto+Sans+HK&display=swap" rel="stylesheet" >

hrefURL地址上配合font-display一起使用&display=swap

font-display它还能实现类似于Font Loading APIBram Stein's Font Face Observer这种第三方脚本实现的功能。

用一张简单的图来描述font-display属性对浏览器加载字体的相关影响:

特别声明,上图来自于@monica的《Font-display》一文。

有关于font-display属性更多的相关介绍可以阅读:

使用text参数

Google Fonts API还有一个特性,应该是提供了text参数。这个很少使用的参数允许你只加载所需要的字符。例如,如果你的文本Logo(使用文本制作的Logo)需要唯一的之际体,则可以使用text参数只加载制作Logo需要的字符:

https://fonts.googleapis.com/css?family=Roboto&text=CompanyName

当然,这种技术非常具体,只有少数实际应用。然而,如果你可以使用它,它可以减少字体的重量高达90%。另外,使用text的参数时,默认情况下只加载normal的字体权重(font-weight)。如果需要使用另外的一个权重,需要在URL中显式的指定它:

https://fonts.googleapis.com/css?family=Roboto:700&text=CompanyName

这几点是@Danny Cooper针对于Google Fonts API做的一些性能优化。其实文章中提到的这些技术方案能否实用于其他的第三方字体库有待于考量,但是对于font-display属性而言,他可以适用于任何一个加载字体的地方。

其他字体加载的优化

很多同学可能会有困惑,自己在Web中开发时,使用到Google Fonts API的场景并不多,甚至是没有。那么要怎么来优化字体的加载呢?事实上还是有很多字体加载的优化方式值得我们去探究和深挖。

CSS字体加载API

很早之前,@Bram Stein提到使用fontFaceObserver来对字体加载做优化。现在,我们可以不借助任何的Polyfill,在浏览器中使用原生的JavaScript API也可以做相关的优化,比如FontFaceSet.load()

如果你担心浏览器是否支持该API,可以先对其做相应的判断:

;(function () {
    if (!('fonts' in document)) return;
})();

如果不支持,那程序会直接return掉,释放程序。该方法使用promise在加载字体后运行函数。该函数将会传递font-sizename作为参数,并使用.then()设置函数,该函数将在加载字体后运行。比如下面这个示例:

;(function () {
    if (!('fonts' in document)) return;
    document.fonts.load('1em PT Serif').then(function () {
        document.documentElement.className += ' fonts-loaded';
    });
})();

在函数中,我们将在html元素中添加.font-load类,它将激活自定义字体。

另外该方法还可以为字体在浏览器中设置一个缓存的期间:

;(function () {
    if (!('fonts' in document)) return;
    document.fonts.load('1em PT Serif').then(function () {
        var expires = new Date(+new Date() + (7 * 24 * 60 * 60 * 1000)).toUTCString();
        document.cookie = 'fontsLoaded=true; expires=' + expires;
        document.documentElement.className += ' fonts-loaded';
    });
})();

在页面加载时,如果cookie存在,将立即添加.font-load类到html元素中并结束程序。

注意,load()方法仅适用于现代浏览器,但在Edge和IE支持。如果你想使用该方法来加载自定义字体的话,可以考虑将FontFaceSet.load()fontFaceObserver结合起来:

;(function () {

    // Native behavior
    if ('fonts' in document) {
        document.fonts.load('1em PT Serif').then(function () {
            var expires = new Date(+new Date() + (7 * 24 * 60 * 60 * 1000)).toUTCString();
            document.cookie = 'fontsLoaded=true; expires=' + expires;
            document.documentElement.className += ' fonts-loaded';
        });
    }

    // Fallback for IE/Edge
    else {
        // Use fontFaceObserver
    }

})();

如果你对这方面的知识感兴趣的话,还可以阅读下面相关文章:

字体加载策略

@zachleat早在2016年对于@font-face的使用时加载字体就探讨过有关于字体加载方面的策略,用文章中的一张图来描述:

最近,@zachleat在他的新博客中以CSS-Tricks网站为例,介绍了CSS-Tricks中是如何使用CSS相关的技巧为开发提供了比较健壮的字体加载策略。

文章中提到的一些策略(或者说技术手段)和@Danny Cooper文章中提到的有点类似。比如<link>标签中rel指相应的属性(如preload)、CSS的font-display,CSS字体加载API(如FontFace)等。文章中详细讨论了如何为两个阶段加载确定不同特性的优先级。但是实现起来难度并不大,代码非常简单。

首先,在第一阶能做的事情。

预加载HTML,可以使用link标签的rel属性来指定加载姿势:

<link rel="preload" href="Rubik-Bold-kern-latin.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="Rubik-Regular-kern-latin.woff2" as="font" type="font/woff2" crossorigin>

另外使用到@font-face的CSS内联到页面中(放置在</head>)中:

@font-face {
    font-family: Rubik;
    src: url(Rubik-Bold-kern-latin.woff2) format("woff2"),
    url(Rubik-Bold-kern-latin.woff) format("woff");
    font-weight: 700;
    font-display: swap;
}
@font-face {
    font-family: Rubik;
    src: url(Rubik-Regular--kern-latin.woff2) format("woff2"),
    url(Rubik-Regular-kern-latin.woff) format("woff");
    font-weight: 400;
    font-display: swap;
}

第二阶段就是借助JavaScript来做相关的优化,这里指的就是CSS字体加载相关的API。你可以把这段脚本放到任何你想放的地方。也可以内联到<head>中:

if( "fonts" in document ) {
    var regular = new FontFace("Rubik", "url(Rubik-Regular-hint-all.woff2) format('woff2'), url(Rubik-Regular-hint-all.woff) format('woff')");
    var bold = new FontFace("Rubik", "url(Rubik-Bold-hint-all.woff2) format('woff2'), url(Rubik-Bold-hint-all.woff) format('woff')", { weight: "700" });
    Promise.all([ bold.load(), regular.load() ]).then(function(fonts) {
        fonts.forEach(function(font) {
            document.fonts.add(font);
        });
    });
}

如果你有更重要的资源需要加载的话,建议到这段脚本之前,以免被阻塞。前面也提到过了,CSS字体加载相关的API不是所有浏览器都支持,如果你使用了该方面的API来对字体加载做相应的优化的话,那么还可以考虑像下面这样的方式为不支持CSS字体加载的API提供相应的降级方案:

if(!("fonts" in document) && "querySelector" in document) {
    // Awkwardly dump the second stage @font-face blocks in the head
    var style = document.createElement("style");

    // Note: Edge supports WOFF2
    style.innerHTML = "@font-face { font-family: Rubik; src: url(/rubik/Rubik-Regular-hint-all.woff2) format('woff2'), url(/rubik/Rubik-Regular-hint-all.woff) format('woff'); } @font-face { font-family: Rubik; font-weight: 700; src: url(/rubik/Rubik-Bold-hint-all.woff2) format('woff2'), url(/rubik/Rubik-Bold-hint-all.woff) format('woff'); }";
    document.querySelector("head").appendChild(style);
}

字全加载优化其他资料

字体加载的不同策略直接会影响到网站的性能,用户的体验。如果你平时的项目中常常会用到@font-face来加载一些Web非安全字体,或者使用一些字体图标。那么就很有必要多了解字体加载、字体性能优化方面的相关知识和技巧。如果你感兴趣的话,建议花一些时间阅读下面这些文章:

CSS 字体新玩法之彩色字体

如果说,使用CSS相关的特性能实现上图这样的彩色字体效果,会不会感到非常的惊讶和好奇。是的,不会错的,在CSS Fonts Module Level 4工作草案中提供了一些新特性,即为Color Font提供了相应的描述。

选择字体配色:font-palette

彩色字体通过 CPAL 表是可以拥有多种不同的配色方案的。font-palette 有三个内置的参数以及支持自定义配色来达到修改配色方案的效果。

  • normal:浏览器尽可能地将该字体当作非彩色字体进行渲染,并选择一个最适合阅读的配色方案。浏览器在做决策时还可能将当前设定的字体颜色color加入决策条件中。还有可能自动生成一组未内置在字体中的配色方案进行渲染。
  • light:一些彩色字体在其元数据中标明某个配色方案适用于亮色(接近于白色)背景中。使用此数值,浏览器将会直接使用标记了该特性的首个配色方案进行渲染。如果字体文件格式无元数据或时元数据中未标记相应的配色方案,那么此时该数值的行为与 normal 相同
  • dark:正好与light 相反
  • 自定义:上面我们介绍了三种基本的配色选择,那么如果要使用其他的配色方案或是要自定义,我们将要借助接下来介绍的@font-palette-values的帮助。

自定义字体配色:@font-palette-values

@font-palette-values用于定义指定字体的配色规则。它允许开发者不仅可以自由选择字体内置的各种配色方案,还能自定义配色方案。而font-palette选择自定义配色方案也是通过本规则设置。

它的基本定义规则是@font-palette-values namename 即为本配色规则的自定义规则名称。

如果你对彩色字体感兴趣的话,可以阅读早前整理的一文章

字体变体font-variation-*

在CSS中可以通过font-variant-*属性来控制大多数OpenType特性。font-variant-*主要包括:

  • font-variant-ligatures
  • font-variant-caps
  • font-variant-numeric
  • font-variant-alternates
  • font-variant-east-asian

这里不对字体变体做过多的阐述,如果你对这面感兴趣,可以阅读《字体变体font-variation-*》一文。这里给大家提供一个案例,让大家有一个更形象的体感:

CSS中的镜像

前端时间@袁川 老师再次向大家展示了CSS的艺术。即使用CSS制作中国式的窗棂:

在制作窗棂时使用到了CSS的-webkit-box-reflect属性。印象中最早接触该属性的时候大约是在2014年的时候,就尝试着使用了这个属性来实现一些倒影或镜像的效果

而在CSS中实现镜像效果的技术方案不仅局限于-webkit-box-reflect属性。接下来,简单的聊聊CSS中的镜像。

CSS Transform实现镜像

最简单的方式就是使用CSS的transform中的scaleX(-1)实现水平镜像,scaleY(-1)实现垂直方向的镜像效果。比如你有一张这样的图:

使用上面提到的方法,可以很容易的实现水平和垂直方向的镜像效果

-webkit-box-reflect实现镜像

该特性是真正用来制作镜像的。其主要包括以下几个属性值:

  • none:此值为-webkit-box-reflect默认值,表示无倒影效果;
  • <direction>:此值表示-webkit-box-reflect生成倒影的方向,主要包括以下几个值:above生成的倒影在对象(原图)的上方;below生成的倒影在对象(原图)的下方;left生成的倒影在对象(原图)的左侧;right生成的倒影在对象(原图)的右侧;
  • <offset>:用来设置生成倒影与对象(原图)之间的间距,其取值可以是固定的像素值,也可以是百分比值,如:使用长度值来设置生成的倒影与原图之间的间距,只要是CSS中的长度单位都可以,此值可以使用负值;使用百分比来设置生成的倒影与原图之间的间距,此值也可以使用负值
  • <mask-box-image>:用来设置倒影的遮罩效果,可以是背景图片,也可以是渐变生成的背景图像。

比如@袁川 老师在教程中向大家演示的案例,就是使用该特性制作的一个中国式窗棂效果

CSS element()函数

CSS Image Values and Replaced Content Module Level 4中有一个element()函数。 这个函数可以将网站中的某部分当作图片渲染。当一个DOM元素在浏览器中得到正确的渲染时,其实得到的就是一张图片。而且元素修改之后,得到的图片也会立马改变。

使用该函数配合transform可以像-webkit-box-reflect一样实现镜像效果:

在CSS中,不管是-webkit-box-reflect,还是transfrom: scaleX(-1)(或transform: scaleY(-1)),甚至element()函数之类的,结合CSS Making相关的特性或者CSS渐变相关的特性会让镜像效果更佳。比如 @ANA TUDOR在她的教程《The State of CSS Reflections》(译文)中向大家演示的案例

如果你对CSS中实现镜像相的技术感兴趣的话,还可以阅读下面相关教程:

小结

Web技巧系列第10期中,我们聊天了排版相关的技巧。而排版中或者说Web制作中都离不开字体的使用。在之一期中我们主要和大家一起探讨了Web中怎么使用@font-face来加载非安全的Web字体(也就是自定义字体,或系统中不具备的字体)。@font-face除了可以让我们使用带有艺术范或个性化的字体之外,还可以使用它来实现字体图标。不管家文本还是图标都会涉及到字体的制作与转换,因此简单的一起聊了一下怎么制作字体和转换字体。

既然使用@font-face特性,都将面临字体的加载,不管是线上(从CDN加载字体)还是加载本地字体,都要考虑怎么做字体加载。所以这一期中,大部分篇幅和大家一起聊聊怎么来优化字体加载,提高页面性能,从而改善用户体验。

最后再向大家展示了CSS另一方面的能力,即,怎么使用CSS的transform-webkit-box-reflectelement()等来实现镜像效果。最后,希望这一期中讨论的东西,大家会喜欢。如果您在这方面有相关的经验,或有较好的建议,欢迎在下面的评论中与我们一起讨论。