webfont解剖
- Unicode字体可以包含数以千计字形
- 有四个字体格式: WOFF2, WOFF, EOT, TTF
- 一些字体格式需要使用GZIP压缩
一个web字体是字形的集合,且每个字形是一个描述了一个字母亦或符号的矢量图。
所以,一个字体文件的大小由两个因素决定:每个字形矢量路径的复杂程度和每个字体所包含的字形数量。
例如,Open Sans, 最流行的web字体之一, 包含了897个字形,包含了拉丁,希腊和古代斯拉夫语字母。
当选择一个字体的时候,重要的是考虑哪些字符集被支持。
例如, Google’s Noto font family 目标是支持世界上所有的语言. 但是,注意Noto的大小,因为包括了所有的语言,所以压缩后大小是130MB+。
Web字体格式
网上如今四个web字体格式: EOT, TTF, WOFF, 和 WOFF2.
不幸的是,虽然有很多的选择,但是没有一个字体是可以在旧的浏览器和新的浏览器上通用的:EOT只适用于IE,TTP部分被IE支持。WOFF享有最广泛的支持但是在老的浏览器中不必支持,并且对于很多新的浏览器真在添加对于WOFF 2.0的支持。
所以,这意味着我们需要使用多个字体格式来保持良好的用户体验:
- 在支持WOFF 2.0的浏览器中使用WOFF 2.0 变体
- 在大多数浏览器中使用WOFF字体
- 在老的安卓(低于4.4)浏览器中使用TTF变体
- 在老的IE(低于IE9)中使用EOT变体
使用压缩来减少字体大小
A font is a collection of glyphs, each of which is a set of paths describing the letter form. The individual glyphs are, of course, different, but they nonetheless contain a lot of similar information that can be compressed with GZIP, or a compatible compressor:
- EOT, 和 TTF 格式默认没有压缩:确保你的服务器使用了GZIP压缩
- WOFF 有内置的压缩 - 确保你的WOFF压缩器使用了最优化的设置。
- WOFF2 使用了用户预定义和压缩算法来减少相对于其他字体格式约30%的文件大小
值得注意的是一些字体格式包括了额外的信息——比如font hitting和kerning信息,这些可能是不必要的,所以可以进行深入优化。
查看你的字体编译器可操作的选项,如果你可以优化上面的内容,进行优化,并在不同的浏览器中查看效果。
Note
- 可以使用 Zopfli compression 来压缩EOT, TTF,和 WOFF 字体格式. Zopfli 是一个 zlib兼容的压缩器,压缩的内容比gzip压缩后还要小约5%。
使用 @font-face定义字体
- 使用
format()定义不同的字体格式
- 使用大量的子集提高性能: 提供大量可选的子集可以让旧的浏览器兼容
- 减少样式化的字体变体来提高页面和文字渲染效果
css语句@font-face定义特殊的字体资源的位置,字体样式等。
字体样式选择
每个 @font-face 声明提供了font family的名字,字体属性的什么和特殊字体来源地址的声明。
@font-face {font-family: 'Awesome Font';font-style: normal;font-weight: 400;src: local('Awesome Font'),url('/fonts/awesome.woff2') format('woff2'), url('/fonts/awesome.woff') format('woff'),url('/fonts/awesome.ttf') format('ttf'),url('/fonts/awesome.eot') format('eot'); }@font-face {font-family: 'Awesome Font';font-style: italic;font-weight: 400;src: local('Awesome Font Italic'),url('/fonts/awesome-i.woff2') format('woff2'), url('/fonts/awesome-i.woff') format('woff'),url('/fonts/awesome-i.ttf') format('ttf'),url('/fonts/awesome-i.eot') format('eot'); }
上面一个 Awesome Font 字体的两个表现形式类型:正常和斜体,每个都提供了一系列的不同字体资源设置。
每个src描述符包括了一个按照优先顺序排序的,用逗号进行分隔的资源列表:
local()
表明我们可以使用哪些本地的字体-
url()
指定我们加载哪些外部字体,允许我们使用format()来指代字体的格式
Note
- 除非你指向系统的默认资源,除非很少有本地的字体资源,特别是对手机设备而言,不可能在内部安装了额外的字体。所以,你总是需要提供一个额外的字体位置清单。
浏览器按照顺序执行,上例浏览器执行如下:
- 浏览器显示页面布局并决定哪个字体变形被需要来渲染指定的字体;
- 依次检查local()中的字体是否存在并可使用; 如果本地不存在,依次检查外部字体定义:
- 如果字体格式存在,在初始化和下载字体之间检查浏览器是否支持,如果不支持,进入下一个外部字体选项检查
- 如果字体格式不存在,浏览器下载资源
Note
- 外部字体加载的顺序十分重要,因为浏览器是按照这个顺序依次执行的。
Unicode-range 子集
我们可以将一个大的字体设置成小的子集(例如拉丁,希腊和斯拉夫字母等),并且只是加载这个子集来渲染页面上的字体
这个 unicode-range descriptor 允许我们指定一个逗号隔开的数据集合, 数据可以是下面三种形式中的任何一种:
- Single codepoint (e.g. U+416)
- Interval range (e.g. U+400-4ff): indicates the start and end codepoints of a range
- Wildcard range (e.g. U+4??): ‘?’ characters indicate any hexadecimal digit
例如我们可以将Awesome Font设置成拉丁和日本语的子集格式,它们只有在浏览器需要的时候才会加载
@font-face {font-family: 'Awesome Font';font-style: normal;font-weight: 400;src: local('Awesome Font'),url('/fonts/awesome-l.woff2') format('woff2'), url('/fonts/awesome-l.woff') format('woff'),url('/fonts/awesome-l.ttf') format('ttf'),url('/fonts/awesome-l.eot') format('eot');unicode-range: U+000-5FF; /* Latin glyphs */ }@font-face {font-family: 'Awesome Font';font-style: normal;font-weight: 400;src: local('Awesome Font'),url('/fonts/awesome-jp.woff2') format('woff2'), url('/fonts/awesome-jp.woff') format('woff'),url('/fonts/awesome-jp.ttf') format('ttf'),url('/fonts/awesome-jp.eot') format('eot');unicode-range: U+3000-9FFF, U+ff??; /* Japanese glyphs */ }
Note
- Unicode-range子集设置对于亚洲语言非常重要,因为亚洲语言的字形比西方语言要多得多,一个字形百万字节,而不是亚洲字符的千字节。
使用unicide range子集并将文件分成字体不同样式的变体让我们可以更加迅速和高效的下载——用户只需要下载子集和变体就可以,用户不需要下载永远不会用到的字体。
但是,不是所有的浏览器支持unicode-range字段。一些浏览器忽略了unicode-range字段,并且将下载所有的字体;另一些浏览器压根儿不会执行@font-face声明。所以,我们对于旧的浏览器需要“手动设置子集”。
因为旧的浏览器不够聪明来选择需要的字体子集并且不能合成一个合适的字体,所以我们必须回退来提供一个单一的字体资源,这个字体资源包括了所有必要的子集,并且对浏览器而言隐藏了其他的子集。
例如,如果网页只是使用拉丁文子集。我们可以剥夺其他的字形,并且将一个指定的子集设置成单独的资源。
- 我们怎样判断哪些字体子集是我们需要的?
- 如果unicode-range子集是被浏览器支持的,那么浏览器就会在自动的选择合适的子集,页面只需要提供子集文件和在@font-face指定合适的unicode-ranges
- 如果unicode-range不是浏览器支持那么页面需要隐藏所有不必要的子集——例如,开发者必须指定需要的子集。
- 我们在什么样的情况下创造子集?
- 使用开源工具 pyftsubset tool 来设置和最优化你的子集.
- 一些字体提供商允许你在用户自定义参数中手动设置字体子集,你可以通过这个方法来手动的为你的页面创建需要的字体——访问字体提供商的页面!
字体选择和合成
每个字体家族都是包含很多样式的字体变体(例如常规,加粗,斜体等等),并且每一种字体变体都有字体权重设置,所以,这将导致了很多不同的字形——例如,不同空格,字体大小都将导致同一个字形变得不同。
例如,上面的图形展示了一个字体在三种不同的权重下的表现:400(普通),700(加粗),900(额外加粗)。
所有的在三个标准之间的权重(灰色显示的)将通过浏览器自动的适应最贴近的三种标准变体。
上述逻辑同样适用于斜体字体,但是要记住,字体变形数量使用越少越好!
@font-face {font-family: 'Awesome Font';font-style: normal;font-weight: 400;src: local('Awesome Font'),url('/fonts/awesome-l.woff2') format('woff2'), url('/fonts/awesome-l.woff') format('woff'),url('/fonts/awesome-l.ttf') format('ttf'),url('/fonts/awesome-l.eot') format('eot');unicode-range: U+000-5FF; /* Latin glyphs */ }@font-face {font-family: 'Awesome Font';font-style: normal;font-weight: 700;src: local('Awesome Font'),url('/fonts/awesome-l-700.woff2') format('woff2'), url('/fonts/awesome-l-700.woff') format('woff'),url('/fonts/awesome-l-700.ttf') format('ttf'),url('/fonts/awesome-l-700.eot') format('eot');unicode-range: U+000-5FF; /* Latin glyphs */ }
上面的例子声明了一个 Awesome Font 家族字体,包含了拉丁字形,但是拥有两种不一样的权重:normal(400)和blod(700)。字体不同的形式实现的原理是什么呢?
- 如果给出的字体权重不是标准的(400,700和900),那么将随浏览器匹配最接近标准的字体形式。
- 如果字体斜体匹配没有发现(例如,我们没有在上例中声明任何的斜体变形),那么浏览器近合成它的自有的字体变形。
必须要知道的是,有些字体通过合成的方法合成的字体会十分难看(比如Cyrillic合成斜体),所以最好使用已经存在的斜体,而不是浏览器自动合成。
上面的例子解释了实际斜体和浏览器合成的斜体之间的不同(字体是Open-Sans字体)。
你可以在结果中看见显著的不同,不同的浏览器合成字体不同并且不同的字体之间合成的字体也不同。
Note
- 为了最好的用户体验,我们应该尽量不要使用字形变体,我们最好指定字体的位置让页面来下载它们。总而言之,使用浏览器合成的变体必须非常小心。
优化下载和渲染
- 字体加载请求会在渲染树(the render tree)被创建之后,这可能导致字体渲染被延迟。
- 字体加载API同样也允许我们实现用户自定义的字体加载和渲染政策来重写和替代默认的字体延迟加载
- Font inlining 允许我们在旧的浏览器中重新书写字体加载形式
一个完整的web字体包含了所有形式的变体,和所有的字形,但是有些字形和字体的变体我们可能不需要,导致下载完整的web字体需要多个百万字节。
所以使用@font-face是我们可以加载指定范围的web字体,使用unicode 子集,独特的字体变形等等。
给出这些申明后浏览器可以加载指定的字体来进行渲染,虽然很方便,但是如果我们不小心的话,会导致在渲染时产生障碍——这些是我们需要避免的!
web字体渲染方式
我们知道字体的渲染必须在浏览器创建了渲染树(就是DOM树和CSSOM树)之后,所以,也就是说字体渲染必须等到所有的资源都被抓取之后
- 浏览器情趣HTML2文档
- 浏览器开始解析HTML响应和构造DOM
- 浏览器你发现CSS,JS和其他的资源并且分配请求 当所有的css内容都被接受并且将其和DOM树结合在一起之后构建CSSOM。
- 当树DOM树被渲染的时候字体被请求,说明了字体变体被渲染到指定的文本上。
- 如果浏览器不可以获得字体吗,那么将不会渲染任何的文本像素
- 一旦浏览器可以获得字体,浏览器将会渲染文本像素
不同的浏览器在渲染的时候执行的操作不一样:
- Safari 在文本下载全部完成之后才进行文本渲染
- Chrome 和 Firefox 在你使用一个回退字体时,进行三秒钟的字体渲染, 并且一旦字体下载完成,浏览器用刚下载的字体再次进行渲染
- IE马上渲染回退字体如果请求的字体不可获得,并且一旦字体加载全部完成之后在进行再次渲染 。
不同的人有不同的喜好——有些人喜欢马上进行渲染有些人喜欢在回退字体加载后进行再次渲染——我们不做任何的评论,但是我们关注的是在布局加载时进行的字体渲染怎样才能进行优化?
使用字体下载API来优化字体渲染
Font Loading API 提供了一个编程脚本来定义和操作CSS字体,追踪字体的下载进程和重写字体的懒加载行为(懒加载是指在DOM树和CSSOM树加载完成之后才进行字体加载渲染的行为)。
例如,如果我们确保一个特殊的字体变形会被请求,我们可以定义它并且告诉浏览器来初始化即时加载字体资源行为:
var font = new FontFace("Awesome Font", "url(/fonts/awesome.woff2)", {style: 'normal', unicodeRange: 'U+000-5FF', weight: '400' });font.load(); // don't wait for render tree, initiate immediate fetch!font.ready().then(function() {// apply the font (which may rerender text and cause a page reflow)// once the font has finished downloadingdocument.fonts.add(font);document.body.style.fontFamily = "Awesome Font, serif";// OR... by default content is hidden, and rendered once font is availablevar content = document.getElementById("content");content.style.visibility = "visible";// OR... apply own render strategy here... });
而且,因为我们可以检查字体的状态(font status)通过check()方法,并且追踪下载的速度,我们同样可以定义一个自定义的策略来在我们的页面上渲染文本,
- 我们可以知道字体可以使用时才渲染所有文本
- 我们可以对于每一个字体设置定时器timeout
我们可以在不同的页面上使用不同的策略。
Note
- 字体加载API在某些浏览器中还尚在开发中,可考虑使用FontLoader polyfill, 或者 webfontloader library, 来产生相同的效果
使用inlining来优化渲染字体
- 在构建CSSOM对象的时候,css样式中匹配设备的查询将作为首选自动加载
- 将字体数据嵌入css样式表中将强制浏览器来加载字体,并且以高优先级进行不用等到渲染树的完成
但是 inlining策略不够灵活并且不允许针对不同个页面我们来定义用户自定义的定时器和渲染策略。为了最好的效果,将你的嵌入字体数据的css独立成css文件,并且加载的时候设置max-age属性,这样当你更新css文件的时候不用强迫你的用于再次下载你的字体。
Note
- 使用inlining策略! 记得上面说的懒加载么?浏览器通常都是在加载DOM和形成CSSOM对象之后,形成渲染树之后在渲染数据内容到屏幕上的!将字体数据放在CSS中,也就是放在CSSOM对象形成之时!
使用HTTP缓存优化你的字体再使用
字体资源是静止的资源,所以可以再次使用,使用http缓存,为字体设置较长时间的max-age。没有必要将字体存储在localStorage中,这会影响性能。
优化总结列表
使用web字体除了会进行下载和下载不必要的资源之外,不会影响页面的渲染和性能!所以放心大胆使用,下面是一些优化建议:
- 审查和监听你的字体: 每个网页中不要使用太多的字体,每个字体都要简化它们的变体数量。
- 使用字体资源中的子集: 没有必要下载整个字体资源,使用特定的字体子集。特别是对于亚裔字体而言,一定要加载子集。
- 对每个浏览器而言使用最优化的字体格式: 每个字体都应该提供 WOFF2, WOFF, EOT, 和 TTF的字体格式. 确保对 EOT 和 TTF进行GZIP压缩,因为默认它们是没有进行压缩的
- 使用缓存策略: 字体资源是静态的,所以记得使用max-age来设置http缓存
- 使用字体加载API来制定字体渲染方式: 默认的加载方式是字体最后渲染,字体加载API允许我们重写对于特定字体的渲染行为,对于不同的网页我们可以自定义渲染方式亦或设置一个定时器策略。对于旧的不支持字体加载API的浏览器,我们可以使用webfontloader的js库亦或使用css嵌入策略。