本章内容
- web字体如何赋予页面独特的体验
- 使用Google Fonts API
- 调整字体的间距(字距和行距)
- web字体性能相关因素与优化
- 处理FOUT和FOIT
网页设计,成也字体,败也字体。几年前,web开发者只能从有限的一些字体中做选择,即所谓的web安全字体
。这些字体类型包括Arial、Helvetica、Georgia等,大部分用户的系统都会安装。因为浏览器只能使用这些系统字体渲染页面,我们必须使用它们。我们可以指定非系统字体,比如Helvetia Neue,但只有那些碰巧安装了这款字体的用户才能正确显示,其他用户只能看到通用的回退方案。
随着web字体的兴起,情况就变了。web字体使用@font-face
规则,告诉浏览器去哪里找到并下载自定义字体,供页面使用。原本平淡无奇的页面,使用自定义字型之后,可能就改观了。这仿佛打开了一个新世界,我们比过去多了非常多的选择。
使用不同的字体可以使页面产生不同的观感,可以活泼也可以严肃,可以沉稳可信也可以不拘小节。看一下图13.1的字体示例,相同的文字设置了三对不同的字体。左上角的例子,标题使用News Cycle,正文使用EB Garamond。这样看起来比较正式,一般在新闻网站出现。右上角的例子使用了Forum和Open Sans,看上去就不会那么正式。这些字体可能会用在个人博客或者小型技术公司。左下方的例子使用了Anton和Pangolin,显得比较活泼,甚至有点跟动画片似的,适合孩子们的网站。只换了一下字体,别的什么也没做,我们就可以改变整个页面的氛围。
图13.1 使用不同字体可以对网站的整体感觉产生显著的影响
[p330 图一]
本章将会讲解web字体。我们会展示web字体如何工作,并推荐一些在线服务,它们可以提供很多字体供你选择使用。我们同时也会学习一下用来控制字体排版、间距和大小的CSS属性。理解这些属性可以让你提升网站的可读性,也可以更好地实现设计师提供的设计稿。
排版设计是一种艺术形式,和印刷出版一样历史悠久。所以本章讨论的内容是这本书中唯一具有几百年历史的话题。既然这样,我们不打算太过详尽地阐述这部分内容,但会介绍一些要点,以及如何应用到现代web中。
通过在线服务使用web字体是最简单也最普遍的方式。常见的有:
- Typekit(www.typekit.com/)
- Webtype(www.webtype.com)
- Google字体(https://fonts.google.com/)
无论付费还是收费,这些服务都为你解决了很多问题,包括技术上(托管服务)和法律上(授权许可)的一些事项。它们都提供了可以选择字体的大型字体库。但有时候如果你需要某些特定字体,可能需要开通特定服务才可以使用。
Google字体有很多高质量并且开源的字体(而且还免费),我们建议你使用这项服务来为网页添加web字体。Google做了大量的工作,所以添加字体变得非常简单。然后我们会深入研究到底是如何工作的。
我们还是使用前一章制作的网页,添加web字体来改进设计。完成后的网页将会如图13.2展示的这样。页面的大部分内容使用的是Roboto作为正文主体字体,标题使用的是Sansita。
图13.2 应用了Sansita和Roboto web字体的页面的其中一部分
[p331 图一]
标题和正文分别使用不同的字体,这是比较常见的做法。通常一种字体是衬线
(serif)体,另一种是非衬线
(sans-serif)体,但示例中的两种都是非衬线体。你也可能会见到有的设计中标题和正文使用了相同的字体,但是不同的字号。
衬线
——字母笔画末端的小线条或者“爪状”装饰。包含衬线的字体就被称为衬线字体(例如Times New Roman)。如果没有衬线,那就是非衬线字体(例如Helvetica)。
如果你已经学完了上一章,那你应该已经做完了页面除了web字体以外的部分(页面的HTML代码详见清单12.1,CSS代码分布在第12章剩下的清单中,所以你的页面应该已经按这些代码渲染好了)。下面我们来添加web字体。
访问https://fonts.google.com/,就可以看到Google字体中可以使用的字体目录。网站使用文字栏的网格来展示这些字体(如图13.3)。你可以直接从网页上选择字体,也可以点击右上角的放大镜来搜索特定字体。
图13.3 Googl Fonts的字体选择界面
[p332 图一]
对于想要使用的字体,点击红色的加号图标,Google就会添加至已选择字体,罗列到右下方的抽屉弹层里(图13.4)。点击红色的减号图标就可以移除字体。
如果你知道自己需要的字体是什么,就可以通过名字搜索字体。在搜索菜单中输入Sansita
,主区域里的其他字体栏就都被过滤掉了。点击加号图标,把它添加到已选择字体。然后在搜索框里删除Sansita并输入Roboto
,Google将找到几款相关的字体,其中包括Roboto、Roboto Condensed、Roboto Slab等。把Roboto添加到已选择字体。
图13.4 抽屉弹窗中展示当前已选择字体,并附带示例代码片段
[p333 图一]
如果你打开已选择字体弹窗,里面会显示Sansita和Roboto,还附带一些HTML(在网页中嵌入字体)和CSS(在样式中使用字体)的代码片段。在使用代码片段之前,你还需要修改一下字体,挑选适合网页的字重。点击“自定义”(Customize)选项卡查看可选项(图13.5)。
你可能习惯于使用标准和粗体,但有些字体设计成多种不同的字重。比如,Roboto有六种不同的字重,范围从纤细(thin)到最重(black),同时每种字重还有对应的斜体格式。勾选想要下载的字体旁边的复选框。
图13.5 选择为网页引入哪种字重和字体样式
[p334 图一]
注意
字型
(typeface)和字体
(font)这两个术语经常被混为一谈。字型
通常是指字体(比如Roboto)的整个家族,一般由同一个设计师创造。一种字型可能会存在多种变体和字重(比如细体、粗体、斜体、压缩,等等),所有这些变体,每一种都可称之为一种字体
(font)。
理想情况下,你可以下载所有的字体变体,这样在设计网页的时候就会有充足的选择。但当你开始勾选复选框的时候,你会发现加载时间(Load Time)指示器(在右上方)会从“快速”(Fast)变成“适中”(Moderate)再变成“缓慢”(Slow)。选择的越多,浏览器需要下载的字体就越多。web字体是拖慢网页加载时间最大的几个元凶之一,仅排在图片之后。所以你应该谨慎一些,只挑选自己需要的字体。
在Roboto下选择细体300,在Sansita下选择超粗800,这是你即将用在示例中的两种字重。正文中可能经常需要用到斜体版本,但不用提前准备,等到确定网站需要的时候再下载。点击“嵌入”(Embed)选项卡返回到代码片段,你会发现代码已经更新到你选择的指定字重了。
如下面清单所示,复制<link>
标签并添加到页面的<head>
里,这样就为页面添加了一个包含字体描述的样式表。这时候页面上一共有两个样式表:自己原来的样式表和字体样式表。
清单13.1 标签里面是包含Google Fonts的样式表
[p335 代码清单一]
Google通过样式表为页面配置好了web字体需要的设置,然后就可以在自己的样式表中随意使用web字体了。添加web字体之后的页面效果如图13.6所示。
图13.6 使用了Roboto和Sansita字体的页面
[p335 图一]
需要使用font-family
属性来指定Roboto或者Sansita,才能使用字体,下面我们更新一下CSS。在<body>
标签中为正文字体设置Roboto,整个网页将会继承使用;为标题和左上角的Ink首页链接设置Sansita字体。把样式表中的相应部分改成下面清单中的代码。
清单13.2 使用web字体
[p336 代码清单一]
Applies Roboto globally to the page 对页面全局使用Roboto字体
Sets headings to the Sansita font 设置标题使用Sansita字体
Sets the homepage link to Sansita 设置首页链接使用Sansita
因为页面中添加了Google Fonts样式表,浏览器就可以理解这些字体名称指向下载的web字体,并应用到页面。如果你使用其他的web字体服务(比如Typekit),整个过程也是大同小异。这些服务要么提供所需的CSS URL地址,要么提供可以为网页添加CSS的JavaScript片段。
接下来我们将稍微调整一下字体的间距,并分享一些关于加载性能的考量因素。在这之前,先来看看Google Fonts做了什么。
提供字体服务的网站把添加字体的工作做的如此简单易用,但我们依然需要了解一下它们是怎么实现的。先来看看Google提供的CSS文件。在浏览器中打开URL https://fonts.googleapis.com/css?family=Roboto:300|Sansita:800 ,就可以看到Google的CSS。我们复制其中的一部分,如下面清单所示:
清单13.3 Google的字体定义样式表
[p336 代码清单二]
[p337 代码清单一]
The @font-face ruleset, defining a single font for use elsewhere in your page’s CSS 每条@font-face规则定义一个字体,可以在页面里其他CSS中使用
Declares the name for this font 声明字体名称
Defines which font style and font weight this @font-face applies to 设置这条@font-face规则使用的字体样式和字重
Location(s) where the font file can be found 可以找到字体文件的位置
The unicode character ranges this @font-face applies to 这条@font-face规则使用的unicode编码范围
@font-face
规则可以为浏览器定义字体,在页面的CSS中使用。这里的第一条规则实际上是说:“如果页面需要渲染font-family
为Roboto的拉丁字符,这些字符使用了正常的字体样式(非斜体)并且字重为300,那么就使用这个字体文件”。第二条规则类似,定义了一个粗体版本(字重800)的Sansita字体。
font-family
用来设置引用字体的名称,可以在样式表的其他地方使用。src:
提供了一个逗号分隔的浏览器可以搜索的地址列表,以local(Roboto Light)
和local(Roboto-Light)
开头,这样的话,如果用户的操作系统中恰好安装了名为Roboto Light或者Roboto-Light的字体,就使用这些字体。否则,就下载使用url()
指定的woff2字体文件。
注意
Google提供的字体文件也包含其他字符集的类似代码,比如西里尔字母、希腊语和越南语。这些字符集放在其他的字体文件中,如果没有用到,浏览器不会下载它们。原理是一样的,我们这里为了简单省略了这部分。
Google的样式表假设浏览器支持WOFF2格式的字体文件。这是可以的,因为Google通过检查浏览器的用户代理字符串,能够判断出我的浏览器(Chrome)支持这些字体文件。如果我们在IE10浏览器中访问相同的URL,返回的样式表稍有不同,其中会使用WOFF字体。
WOFF
是指Web Open Font Format,Web开放字体格式,这是一种专为网络使用而设计的压缩字体格式。所有的现代浏览器都支持WOFF,但不是所有的都支持WOFF2(WOFF2格式有更好的压缩效果,所以文件更小)。你可能不希望像Google那样,每次都去判断用户代理字符串。下面清单中的代码介绍了一种更通用的解决方式,同时提供WOFF和WOFF2字体文件的URL(为了使代码更易读,这里使用了简写的URL)。
清单13.4 WOFF2 web字体声明,附带回退为WOFF
[p338 代码清单一]
The first supported format listed will be used. 优先使用列表中支持的第一种格式
Fallback to WOFF for browsers that don’t support WOFF2. 不支持WOFF2的浏览器回退为WOFF
web字体刚刚兴起的时候,开发者必须引入四五种不同格式的字体,因为每款浏览器支持的格式都不一样。现在几乎所有浏览器都已经支持WOFF,因为WOFF2加载更快,一般都会提供这两种字体的URL。
如果需要用到同一种字型的多种字体,每一种字体都需要自己的@font-face
规则。如果你在Google Fonts页面上同时选择了Roboto的细体和粗体版本,Google将会提供一个类似https://fonts.googleapis.com/css?family=Roboto:300,700这样的样式表URL。在浏览器中打开URL并查看代码,这里我们拷贝了一部分,如下面清单所示:
清单13.5 定义同一种字型的两种不同字重的字体
[p338 代码清单二]
Roboto light 细体Roboto
Roboto bold 粗体Roboto
清单展示了Roboto字体的两种不同的定义。如果页面上需要渲染字重为300的Roboto,就使用第一种定义;如果需要渲染字重为700的Roboto,就使用第二种。
如果页面样式需要用到其他版本的字体(比如font-weight: 500
或者font-style: italic
),浏览器会从提供的两种字体中选择更接近的字体。但是取决于浏览器,可能会把某个已提供字体倾斜或者加粗来达到想要的效果,使用几何学的方法来实现字母形状的转换。这样的字体显示肯定不如原生设计的效果好,所以不建议依靠这种方式。
在你使用Google Fonts或者其他字体服务提供商的时候,通过界面操作就可以获得所需的代码。有时候可能服务商没有提供你要使用的字体,这种情况下就需要自己提供字体服务,使用@font-face
规则定义浏览器所需的格式。
我们回到页面上。现在web字体加载好了,我们按照设计稿再调整一下。这里涉及到两个属性:line-height
和letter-spacing
,这两个属性可以控制文本行之间的距离(垂直方向)和字符之间的距离(水平方向)。
很多开发者可能不太看重这两个属性。如果在网页实现的时候多花点儿时间调整一下,可能会使整个网站的效果产生很大的改进。除此之外,还可以让用户在阅读的时候体验更好,增加用户黏性。
如果文字间距太紧凑,阅读更多句子或者词语就会比较费劲,间距太大也会有同样的问题。图13.7展示了多种不同间距的文本。
试着读一下左上方的压缩版文字,你会发现需要更集中注意力才行。可能不小心就漏了一行,或者同一行读两次,很快就不想读下去了。这样的页面显得比较拥挤,没有条理。左下方的文字有点太分散了,这样每个字母就会占去太多注意力,不容易在大脑里组合成单词。相比之下,右上方的文字比较舒服,看上去“刚刚好”,是这三个里面最容易阅读的。
图13.7 文字间距对阅读体验有明显的影响
[p340 图一]
为line-height
和letter-spacing
找到合适的值是个主观性很强的事情。最好的解决办法通常是多试几个值,如果两个值要么太紧凑要么太松散,那就取它们的中间值。下面介绍的这些经验可以提供帮助。
line-height
属性的初始值是关键字normal
,大约等于1.2(确切的数值是在字体文件中编码的,取决于字体的em大小)。但是在大部分情况下,这个值太小了。对于正文主体来说,介于1.4和1.6之间的值比较理想。
我们已经在上一章为<body>
设置了1.4的行高,这个值会被页面里其他的元素继承。看看是不是把这个设置弄丢了。图13.8展示了其中一个文字栏。其中左边的这个,line-height
和letter-spacing
都使用原始值,右边的是调整过的(我们的目标是把网页调整成右侧栏目的字距)。
图13.8 Ink页面的一个文字栏,一个使用原始字距(左边),另一个是最终调整值(右侧)
[p341 图一]
把line-height
的值改成1.3或者1.5试一下,看看效果如何,是否比之前的1.4好一些。
小贴士
文字行越长,行高应该设置得越大。这样读者的眼睛扫到下一行的时候才更容易,不会失去焦点。理想情况下,每行文字的长度应该控制在45-75个字符之间,一般认为这样的长度最利于阅读。
接下来,我们看一下letter-spacing
。这个属性需要一个长度值,用来设置每个字符之间的距离。即使只设置1px,那也是很夸张的字间距了,所以这应该是个很小的长度值。我在尝试找到合适值的时候,一般每次只增加1em的1/100(例如,letter-spacing: 0.01em
)。把下面的代码添加到CSS中:
清单13.6 在body元素上设置字符间距
[p341 代码清单一]
Line height and letter spacing will be inherited by everything on the page. 行高和字距会被页面上所有其他元素继承
Adds 0.01 em of extra space between characters 为字符之间添加0.01em的额外间距
把字间距增加到0.02em或者0.03em,看看效果如何。你可能不具备设计师的专业眼光,没办法确定哪种效果最佳,但是没关系,凭直觉就好。如果还是有疑虑,那就保守一点不要设置太大。我们的目的不是要吸引用户注意字间距,事实上恰恰相反。在Ink页面上,我感觉0.01em和0.02em都可以,那我们就保守一点选择0.01em。
把行距和字距转换成CSS
在设计领域,文本行之间的距离被称为
行距
(leading,与bedding有点谐音),来源于印刷版每行文字之间添加的一条条的引导线(lead)。字符之间的距离称之为字距
(tracking)。如果你和设计师一起工作,他们可能会在设计稿中指明行距和字距,但这些值看起来跟CSS属性line-height
和letter-spacing
一点儿都不像。行高一般使用“点”作单位来描述(比如18pt),代表的是一行文字的高度加上它与下一行文字之间的距离。这实际上与CSS的
line-height
一样,但是没有使用一个无单位的数字来表达。你必须首先把它转化为跟字体一样使用像素单位,然后再计算出无单位数字。把点值乘以1.333,就可以把pt转化为px(因为每英寸是96px,并且每英寸等于72pt,96/72=1.333),即18pt*1.333=24px。然后除以字号,得到无单位的行高值,即24px/16px=1.5。
字距通常会给定一个字数,比如100。这个数字表示1em的千分之一,所以除以1000就可以转化成em单位,即100/1000=0.1em。
标题的间距通常和正文主体不太一样。调整完主体间距设置以后,检查一下标题,看看是否需要调整。
标题一般比较短,通常只有几个字,但有时候也可能出现比较长的标题。设计的时候常犯的错误就是只测试短标题。现在页面的行高设置好了,可以试着为各个标题添加额外的文字强制换行看看效果(图13.9)。
图13.9 强制标题换行,确保行高是合适的
[p342 图一]
实际页面中,垂直间距看上去还可以,所以就不再修改了,但一定要记得检查一遍。有时候1.4的行高可能显得有点大,这也取决于字型,特别是设置大字号的时候。我曾经在一些网站上把行高调低到1.0才达到合适的效果。
相对来讲,字间距反而可以设置得稍宽一些。把下面清单中的代码添加到样式表,其中字间距做了一些小调整。
清单13.7 增加标题字间距
[p343 代码清单一]
Increases the letter spacing for headings 增加标题字间距
对于正文主体来讲,调整间距是为了使阅读体验达到最佳。但对于标题和其他内容很少的元素(比如按钮)来讲,影响不大。这时候间距可调整范围大大增加,就可以多多发挥创意了。也可以使用负数设置字间距,让字符更紧凑。图13.10里的标语设置了letter-spacing: -0.02em
。
图13.10 页面上内容简短、风格鲜明的部分,可以考虑使用更紧凑的字间距
[p343 图一]
间距变化还是很明显的。如果是几段文字产生这样的间距变化,可能就会变得难以阅读,但是对于小段文本效果还可以(只有几个字)。我们对标语文字使用这样的效果,添加下面清单中的代码到样式表中:
清单13.8 收紧标语的字间距
[p344 代码清单一]
Uses a negative letter spacing to compress text 使用负值的letter-spacing来压缩文字间距
我们也可以重新评估一下页面上小元素的间距和文本,比如按钮。我认为现在的按钮看着有点太大了,特别是头部的导航按钮。下面我们来调整一下。图13.11展示了现在的效果(上面的图)和调整后的效果(下面的图)。
13.11 调整文本属性可以改善导航按钮的外观(下面的图)
[p344 图一]
这里做了如下调整:减少字号、使用text-transform
把字母改成大写、调大字符间距。
小贴士
全部使用大写字母的文字,调大字符间距看上去更好一些。
把下面清单中的代码添加到样式表。页面上其它的按钮同样使用了缩减的字号,都会变得稍小一些。但对于导航链接来讲,只需要改成大写和调整字符间距。
清单13.9 调整nav
菜单元素的尺寸和间距
[p344 代码清单二]
[p345 代码清单一]
Aligns items in the nav container to the bottom 把导航容器中的元素对齐到底部
Decreases font size of nav links and buttons 减小导航链接和按钮的字号
Changes padding values from em to rem 把内边距值从em换成rem
Capitalizes nav links and increases letter spacing 把导航链接改成大写并增加字符间距
Decreases font size of nav links and buttons 减小导航链接和按钮的字号
因为减小了导航链接的字号,它们不再能填充nav-container
内容盒子的高度。默认情况下这些链接是顶部对齐的,下方留空一些区域。设置nav-container
的弹性子元素对齐到底部(flex-end
)来解决这个问题。
因为导航元素的字号已经改变了,它们的内边距(之前以em为单位来设置)也会随之改变。为了避免这样,我在这里把单位改成rem。当然也可以通过数学计算得出新的以em为单位的相应值,但是并不值得这样做。
text-transform
属性对你来说可能有点陌生。它可以把所有字母改成大写,不管HTML原始文本如何书写。这里强烈推荐这种方式,而不是去HTML里直接把文本改成大写。因为通过这种方式,如果将来设计稿修改了,就可以只改一行CSS,而不必修改所有HTML页面的多个地方。如果是需要遵循某种语法规则的大写(比如首字母缩略词①),就需要修改HTML的内容了。像这种只是单纯地出于设计上的考虑,只使用CSS就可以实现。
【译者注①:首字母缩略词(acronym),比如HTML是由HyperText Markup Language的首字母缩略而成。】
lowercase
是text-transform
属性的另一个值,表示把所有字母都小写。还可以设置为capitalize
,这样只把每个单词的首字母大写,其余字母保持HTML里的原始状态。
更加深入:垂直规律
在第12章中,我们讨论了在设计中贯彻始终如一的图样的重要性,其中也包括屏幕上元素间距的始终如一。
垂直规律
(Vertical rhythm)就是针对整个页面所有文本行应用这一原则的实践方式。这是通过设置基线网格
(baseline grid)来实现的,基线网格是指文本行之间重复等距离的标线。页面上大部分甚至全部的文本都应该参考基线网格来对齐。这张图中使用了等距离水平线标注了基线网格,注意标题、正文文本和按钮文本是如何对齐到网格的。
[p346 图一]
使用各种规格的文本和外间距的元素,却遵守一致的垂直方向上的规律,即基线网格
在网站中引入这种设计原则无疑会增加很多工作量,但回报你的是更加精细的一致性。如果你追求细节,打算自己尝试一下,建议你读一读这篇文章https://zellwk.com/blog/why-vertical-rhythms/。
再提醒一句,创建垂直规律一般需要在
line-height
声明中使用单位。因为这样会改变行高值被继承的方式(详见第2章),你必须确保在页面上所有字号改变的地方明确设置合适的行高。
我们处理字体的时候,必须考虑一下性能,因为字体文件很大。我在前面提到过,页面使用的字体文件数量应该精简到最少,但即使这样,仍然可能存在问题。浏览器中经常会遇到这种情况,页面的内容和布局就要开始绘制了,字体却依然还在下载中。有必要来思考一下这时候会发生什么。
开始的时候,大多数的浏览器提供商为了尽可能快地绘制页面,使用了可用的系统字体。然后,一小段时间过去了,web字体加载完成,页面将会使用web字体重绘一次。图13.12阐述了这个过程。
比起系统字体,web字体很可能会在屏幕上占据不一样的空间。第二次绘制发生的时候,页面布局变了,文字会发生跳动。如果是在第一次绘制之后很快发生,用户可能不会注意到。但是如果字体下载过程中有网络延迟(或者字体文件太大了),可能长达几秒之后才会再次绘制页面。这种情况发生时,有的用户可能会感到厌烦。他们可能已经开始阅读网页内容了,这时候页面突然变化,会让他们失去阅读焦点。这就是所谓的FOUT
,即无样式文本闪动(Flash of Unstyled Text)。
图13.12 FOUT(无样式文本闪动)
[p347 图一]
240 ms: fallback fonts rendered 240ms:回退字体绘制
410 ms: web fonts rendered 410ms:web字体绘制
开发者不喜欢这样,所以大部分浏览器提供商修改了浏览器的行为。他们不再绘制回退字体,改成绘制页面上除了文本以外的其他元素。确切地说,他们把文本绘制成不可见的,所以文字依然会占据页面的空间。通过这种方式,页面的容器元素得以实现,用户就可以看到页面正在加载。这样就导致了一个新的问题,FOIT
,即不可见文本闪动(Flash of Invisible Text)。如图13.13所示,背景颜色和边框都显示出来了,但是文字在第二次绘制的时候才显示,即web字体加载之后。
图13.13 FOIT(不可见文本闪动)
[p347 图二]
240 ms: invisible text rendered 240ms:不可见文本绘制
410 ms: web fonts rendered 410ms:web字体绘制
这种方案解决了之前的问题,但又带来了新问题:如果web字体加载时间很长会发生什么?或者加载失败呢?页面会一直空白,这些彩色的盒子只是空壳,对于用户来讲完全没有意义。这种情况发生的时候,我们还是希望使用之前FOUT时候的系统字体。
开发者针对这些问题提出了很多解决办法,几乎每一年都会涌现出“更好”的方案。要解决问题,其实就是要避免发生FOUT和FOIT。然而在web字体领域,这两个问题从未被彻底解决。我们能做的就是尽可能让它们产生的影响降到最低。
幸好关于这些问题的讨论已经快要尘埃落定,这里我就不从头介绍好几种不同的技巧了,直接演示我认为最合理的解决方案。这里需要用到一点JavaScript来控制字体加载。同时我也会介绍一个即将新增的CSS属性,不需要JavaScript就可以提供这种控制。你可以使用任意一种或者同时使用两种。
使用JavaScript可以监控字体加载事件,这样就可以更好地控制FOUT与FOIT的发生过程。还可以使用js库来帮助处理,我喜欢用一款名叫Font Face Observer的库(https://fontfaceobserver.com/)。这个库可以让你等待web字体加载,然后做出相应的响应。我一般是在字体准备好的时候,使用JavaScript为<html>
元素添加一个fonts-loaded
类。然后就可以使用这个类为页面设置不同的样式,可以用也可以不用web字体。
下载一份fontfaceobserver.js文件,保存到和页面相同的目录下。然后添加下面清单中的代码到页面底部,放在</body>
闭合标签之前。
清单13.10 使用Font Face Observer监测字体加载
[p348 代码清单一]
Dynamically creates a <script> tag to add the Font Face Observer to the page 动态创建
<script>
标签,添加Font Face Observer到页面上Creates observers for both Roboto and Sansita fonts 为Roboto和Sansita字体创建观察器
When both fonts are loaded, adds the fonts-loaded class to the element 两种字体都加载完成以后,为
<html>
元素添加fonts-loaded类When font loading fails, adds the fonts-failed class to the element 如果字体加载失败,为
<html>
元素添加fonts-failed类
这段脚本创建了两个观察器,分别用于Roboto字体和Sansita字体。Promise.all()
方法会等待两个字体都加载完成,然后脚本为页面添加fonts-loaded
类。如果加载失败,或者加载超时(超过两秒),catch
回调函数将会被调用,这时为页面添加fonts-failed
类。这样当页面加载完成的时候,脚本会为页面要么添加fonts-loaded
类,要么添加fonts-failed
类。
注意
这段脚本和Font Face Observer都使用了JavaScript的一个名为promise的特性,但是IE浏览器不支持这个特性。幸运的是,Font Face Observer包含了一个polyfill来添加支持。如果你已经使用了自己的polyfill,那就在它们的网站上下载Font Face Observer的独立版本。
接下来,我们演示一下如何使用fonts-loaded
和fonts-failed
类来控制字体在加载过程中的表现。
限制网络来测试字体加载行为
如果你是在高速网络连接条件下开发,很难测试到网站的字体加载行为。有一种解决方案是在Chrome或者Firefox开发者工具中手动调低下载速度。
在Chrome的Network标签中,顶部条里有个下拉菜单,预先设置了几种网络速度。在选择框中选中Regular 3G(常规3G),就可以手动设置低速网络连接,如下图所示:
[p349 图一]
建议同时勾选Disable Cache(禁用缓存)旁边的复选框,每次加载页面,所有的资源都会重新下载。这样就可以模拟出低速网络条件下,网站最初的页面加载时用户看到的样子。
需要保持开发者工具是打开状态,这些设置才会生效。使用完成之后记得把这些设置恢复到正常,否则下次你打开开发者工具的时候可能会大吃一惊。
针对字体加载,我们可以采用两种基础方案。第一种,在CSS中使用回退字体,然后在选择器中使用.fonts-loaded
,把回退字体改成想要的web字体。这样就可以把浏览器的FOIT(不可见文本)改为FOUT(无样式文本)。
第二种,在CSS中使用web字体,然后在选择器中使用.fonts-failed
,把字体改成回退字体。这种方案依然会产生FOIT,但是如果超时就会转换为系统字体,页面不会在加载失败的时候被不可见文本卡住。
这两种方案,我一般倾向于第二种。但这纯粹是我个人的看法,至于哪种更好,取决于你的偏好或者工作项目的具体内容,甚至设置多久的超时时间也算是喜好问题。
我们来实现一下第二种方案。下面的代码使用.fonts-failed
类添加回退样式,把这些代码加入到你的CSS中。
清单13.11 定义回退样式,FOIT中的文本卡顿又会出现了
[p350 代码清单一]
If web fonts fail to load, falls back to system fonts 如果web字体加载失败,回退到系统字体
字体加载失败的时候(或者加载超时),fonts-failed
类被添加到页面,回退样式就会应用到页面上。网速快的时候,web字体加载之前,会有短暂的FOIT出现。如果网速比较慢,FOIT会持续两秒,然后显示回退字体。
小贴士
我们为调整web字体的字符间距花费了很多时间。你可能也想为回退的系统字体再走一遍同样的流程,因为它们的字符间距很可能是不一样的。可以把这些调整间距的代码放在
fonts-failed
规则集中,这样只有在web字体加载失败的时候才会应用。如果你想再多做一些,可以把回退字体调整成和web字体一样的间距,这样出现FOUT的时候就不会那么明显。https://meowni.ca/font-style-matcher/提供的工具可以帮你做这些。
处理字体加载没什么标准答案。如果你有针对网站加载时间的分析工具,在决定采用哪种方案的时候可能会有些帮助。一般来说,网速快的时候FOIT更容易接受一些,但网速慢的时候应该倾向于FOUT,根据实际情况来判断。
有个新的CSS属性font-display
,目前处于工作草案阶段,可以对字体加载提供更好的控制而不需要JavaScript的干预。截止到本书完稿之时,这个新属性仅在Chrome和Opera浏览器中可用,Firefox即将支持(【译者注】到本书翻译时,Firefox和Safari浏览器都已经支持,可以浏览https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display查看最新的支持情况)。我们会简单介绍一下它是如何工作的,为将来做一些前瞻性的准备。
这条属性需要在@font-face
规则内部使用,用来指定浏览器应该如何处理web字体加载。下面的代码清单展示了一个使用案例。
清单13.12 font-display
属性的一个案例
[p351 代码清单一]
Uses the swap behavior when loading fonts: a FOUT 加载字体的时候使用swap行为,即FOUT
这样会告诉浏览器立即显示回退字体,然后等web字体可用的时候进行交换
(swap),简而言之,就是FOUT。
这个属性同时也支持以下几个值:
auto
——默认行为(在大多数浏览器中是FOIT)。swap
——显示回退字体,在web字体准备好之后进行交换(FOUT)。fallback
——介于auto
和swap
之间。文本会保持隐藏状态一段较短的时间(100ms),如果这时候web字体还没有准备好,就显示回退字体。接下来一旦web字体加载完成,就会显示web字体。optional
——类似fallback
,但是允许浏览器基于网络连接速度判断是否显示web字体。这就意味着在较慢的连接条件下web字体可能不会显示。
这些选项比之前的几行JavaScript提供了更多的控制能力。对于高速网络连接,fallback
表现最好,会出现短暂的FOIT,但如果web字体加载超过了100ms就会产生FOUT。对于低速网络连接,swap
更好一些,会立刻渲染回退字体。如果web字体对于整体设计来讲并非必不可少的时候,可以考虑使用optional
。
如何控制web字体的表现是个比较棘手的问题。如果想要更深入研究,推荐阅读Jeremy L. Wagner写的《Web Performance in Action》
(美国Manning出版社2016年出版)。书中花了一整章来介绍web字体表现,同时有些章节也包含其他跟CSS有关的议题。
- 使用字体提供商(比如Google Fonts)的服务可以轻松进行web字体整合。
- 严格限制添加到网页的web字体数量,控制页面体积。
- 使用
@font-face
规则集管理自己的字体 - 花点时间调整
line-height
和letter-spacing
,使页面段落分明、清晰易读。 - 使用Font Face Observer或其他JavaScript来协助控制加载行为,避免文本隐藏的问题。
- 将来留意
font-display
的支持情况。