SunXinFei / sunxinfei.github.io

前后端技术相关笔记,已迁移到 Issues 中
https://github.com/SunXinFei/sunxinfei.github.io/issues
32 stars 3 forks source link

前端字体优化二三事 #24

Open SunXinFei opened 4 years ago

SunXinFei commented 4 years ago

背景

前端项目的字体文件,尤其是汉字的字体文件还是非常大的,因为不同于英文,只需包含基本的英文字母以及数字之类的即可,一些体积比较小的汉字包含了常用的6500字左右,包含汉字数量越多,汉字质量越高会导致字体文件体积极速膨胀到10M以上,H5手持端以及pc前端页面加载都会显得非常缓慢。尤其是一个页面使用了多个体积庞大的字体文件,会导致加载时间很长。 现象就是用到字体的文本,会一直空白,知道字体加载,才会正常显示。

SunXinFei commented 4 years ago

各种字体格式

  1. truetype - ttf
    • Windows 和 Mac 最常见字体
    • RAW 格式,不为任何网站优化
    • IE9+、Firefox3.5+、Chrome4+、Safari3+、Opera10+、iOS Mobile Safari4.2+

TrueType字体格式由Apple和Microsoft开发,是对PostScript字体格式的响应。长期以来,TTF一直是Mac和Windows操作系统上最常见的字体格式。所有主要的浏览器都支持它。但是,IE8不支持TTF。从IE9开始,仅部分支持该字体,因为必须将字体设置为“可安装”。

TTF支持最基本的数字版权管理类型–可嵌入的标志,用于指定作者是否允许将字体文件嵌入到PDF文件和网站中。有一些工具可以修改此标志,这是有关TTF格式的主要问题之一。另一个缺点是TTF / OTF字体未压缩,因此它们的文件大小较大。

  1. opentype - otf
    • 原始字体格式,内置在 truetype 基础之上
    • 提供更多功能
    • Firefox3.5+、Chrome4.0+、Safari3.1+、Opera10.0+、iOS Mobile Safari4.2+

OpenType是TTF的发展。这是Adobe和Microsoft共同努力的结果。 OpenType字体在一个组件中包含屏幕和打印机字体数据。 OTF具有多种独有功能,包括对多个平台和扩展字符集的支持。 Macintosh和Windows操作系统可以使用OTF字体。

OTF还允许存储多达65,000个字符。这种额外的空间使设计人员可以自由地添加附加组件,例如小写字母,旧式图形,替代字符和以前必须作为单独字体分发的其他附加组件。

  1. web-open-font-format - woff
    • Web 字体最佳格式
    • 是一个开放的 truetype、opentype 压缩版本
    • 支持元数据包的分离
    • IE9+、Firefox3.5+、Chrome6+、Safari3.6+、Opera11.1+

WOFF基本上是所有主要浏览器都支持元数据和压缩的OTF或TTF。它是为生活在网络上而创建的。这是Mozilla基金会,Microsoft和Opera Software合作的结果。由于字体是压缩的,因此加载速度更快。元数据允许将许可数据包含在字体文件中,以解决版权问题。它是万维网联盟的建议书,并且显然是字体格式的未来。

WOFF2是下一代WOFF。 WOFF2格式比原始WOFF提供30%的平均压缩增益。因为它仍然只是建议的升级,所以它没有WOFF的广泛支持。

  1. embedded-opentype - eot
    • IE 专用字体
    • 可以从 truetype 创建此格式
    • IE4+

EOT字体由Microsoft设计为网络上使用的字体。试图解决在Web上发布时TTF和OTF的版权缺陷。 EOT使用Microsoft工具从现有的TTF / OTF字体创建字体。压缩和子设置使字体文件更小。子集提供了一些版权保护,但是EOT也使用加密来提供进一步的保护。听起来很有前途吗?是的,但是Internet Explorer仅支持EOT格式。

  1. svg - svg
    • 基于 svg 渲染
    • Chrome4+、Safari3.1+、Opera10.0+、iOS Mobile Safari3.2+

SVG字体是使用SVG的“字体”元素定义的。这些字体包含字形轮廓作为标准SVG元素和属性,就好像它们是SVG图像中的单个矢量对象一样。 SVG字体的最大缺点是缺少字体提示。字体提示是嵌入的额外信息,可呈现小字体且具有质量和易读性。另外,SVG不适用于正文。由于在Safari,Safari Mobile和Chrome上无法选择文本,因此无法选择单个字符,单词或任何自定义选项。您只能选择整个文本行或段落。

但是,如果您针对iPhone和iPad用户,则SVG字体是您唯一的选择。这是iOS版Safari的4.1版及更低版本所允许的唯一文件格式。

参考:

SunXinFei commented 4 years ago

无样式文本的闪光(FOUT)

一般情况下的字体加载有“闪”的一个过程:常见的症状是短暂的一刻,自定义字体还未下载之前会先隐藏文本,然后闪烁到下载的字体。(被称为“ FOUT”:无样式文本的闪光) 一般我们可以通过限制连接速度,可以观测到浏览器的这种默认行为 这里有三篇文章讲述这个过程:

CSS --神奇的font-display

font-display属性介绍:

/* 关键字值 */
font-display:auto; //字体显示策略由用户代理定义。
font-display:block; //为字体提供一个短暂的阻塞周期和无限的交换周期。
font-display:swap; //为字体提供一个非常小的阻塞周期和无限的交换周期。
font-display:fallback; //为字体提供一个非常小的阻塞周期和短暂的交换周期。
font-display:optional; //为字体提供一个非常小的阻塞周期,并且没有交换周期。

字体阻塞周期 如果未加载字体,任何试图使用它的元素都必须渲染不可见的后备字体。如果在此期间字体已成功加载,则正常使用它。 字体交换周期 如果未加载字体,任何尝试使用它的元素都必须呈现后备字体。如果在此期间字体已成功加载,则正常使用它。 字体失败周期 如果未加载字体,用户代理将其视为导致正常字体回退的失败加载。

eg. 如果使用了swap属性:浏览器完全不会等待字体加载,立刻用字体栈中最符合条件的字体把文字先显示出来。随后如果有更符合的字体,无限等待字体文件加载完毕,浏览器会“交换”字体。

image

使用js探测浏览器是否支持font-display:

if ("fontDisplay" in document.body.style === false) { 
  /* JavaScript font loading logic goes here. */ 
}

总结: 使用font-display的优势非常明显,简单明了的使用,不需引用任何类库,纯浏览器原生的支持,缺点就是兼容性。 font-display属性详细介绍:

JS --引入监听脚本

这类的方法是使用JS脚本(比如 Font Face Observer)来跟踪字体是否被下载。在字体被下载完成前,我们使用浏览器自带的字体来显示文本,直到探测到字体下载完成。 大致思路为: 我们首先定义一个css默认的字体,例如:

p {
 font-family: "Arial", "Helvetica", sans-serif; 
}

然后使用js探测到字体加载完毕之后,给html或body元素添加一个class类(如fonts-loaded)即可:

.fonts-loaded p { 
  font-family: "Open Sans Regular";
}

总结:js探测脚本优势在于兼容性很佳,但是书写起来有些繁琐。

font-display与脚本检测的结合

我们先判断浏览器是否支持font-display以及使用原生api或者 Font Face Observer)来判断字体文件是否加载完毕

if ("fontDisplay" in document.body.style === false) { 
    if("fonts" in document) { 
        document.fonts.load("1em Open Sans Regular"); 
        document.fonts.ready.then(function(fontFaceSet){ 
            document.documentElement.className += " fonts-loaded"; 
        }) 
    } 
}

参考:

SunXinFei commented 4 years ago

静态字体文件压缩

上面提的都是关于体验的优化,但是并没有从根本上解决10几M的超大字体文件的问题,这一类的方式以字蛛为代表大致思路为:使用脚本扫描html页面和css文件,将ttf等字体文件中没有用到字体进行剔除,生成新的字体文件,这样就意味着使用的文字必须显性暴露出来,像H5之类的静态页面就非常适合字蛛来进行压缩,但是如今React和Vue大行其道,所以这种已经不适合了。

参考:

  • font-spider
  • 字蛛实现原理

    动态字体文件压缩

    font-spider-plus为代表的动态分析字体文件。 原理大致为: 使用puppeteer分析线上页面 -> 合并压缩线上页面外联样式 -> 样式添加到线上页面文本流 -> 用font-spider api压缩WebFont
    这种动态分析基本可以满足很多动态元素dom拼接页面的字体文件压缩需求,但是使用了无头浏览器爬虫,可能会丢失模态框里面字体以及对于页面搭建器生成的动态页面,暴露出配置多个动态URL不方便的问题。

参考:

页面搭建工具生成页面的字体优化

页面搭建工具生成的页面有这么几个特点:

  1. 所有落地页虽然不同URL,但是使用相同的css和js,或者说不同落地页加载的文件差别并不会特别大
  2. 页面的数据是配置的json驱动出来,或者是SSR通过配置数据生成静态页面

由于第一条的原因,font-spider-plus并不能满足这种页面的字体优化需求,通过对橙子建站等建站工具的调研,解决方案如下:

  1. 每个落地页在预览/保存时,后端服务会扫描落地页json结构,得到页面中所需要呈现的文字以及展示的字体(font-family)。
  2. 服务通过文字和字体(font-family)生成唯一hash值命名的的css文件。
  3. 相同的文字以及字体(font-family)会使得css文件名称hash相同
  4. 从源字体文件中筛选过滤所需要的字体,并生成新的字体文件并base64编码,写入到css文件中。
  5. 使用多个字体也只会生成一个css文件,只是css文件内存在多个@font-face
  6. 在html中动态加载该css文件,即为<link rel="stylesheet" href="https://lf1-ttcdn-tos.pstatp.com/obj/ad-tetris-site/tetris-fonts/1a66360280e25019454d6abbff21c99c.css">

例如: “封”字=的“装甲明朝体”加载的css字体文件:https://sf6-ttcdn-tos.pstatp.com/obj/ad-tetris-site/tetris-fonts/1a66360280e25019454d6abbff21c99c.css “哈”字的“装甲明朝体”加载的css字体文件:https://lf1-ttcdn-tos.pstatp.com/obj/ad-tetris-site/tetris-fonts/585e769bd3998007c0d432cc57db3f69.css “封”字的“装甲明朝体”与“封”字的“书体坊颜体”加载的css字体文件:https://sf1-ttcdn-tos.pstatp.com/obj/ad-tetris-site/tetris-fonts/6ade98ac58fdbd0dad802b7783ce11e7.css 附:仅供参考的后端字体压缩服务:sfnttool.jar (Java),Fontforge (Python),Font Optimizer (Perl),font-spider(Node) 有字库接口文档其中有字库这个在线的字库就是使用的类似上面提到的方式,进行按需截取字体文件。 通过上述的方案,明显可以将固有的10M以上的字体文件,压缩为几十K。