如何以抵制FOUT和使Lighthouse快乐的方式加载字体

2020-11-27 08:53:32

网络字体工作流程很简单,对吧?选择一些看起来不错的网络就绪字体,获取HTML或CSS代码段,将其放入项目中,然后检查它们是否正确显示。人们每天使用Google字体进行此操作数以千计,将其标记放入中。

我们已经按照书籍,文档和HTML标准完成了所有工作,那么为什么Lighthouse会告诉我们所有错误?

让我们谈谈消除字体样式表作为阻止渲染的资源,并逐步介绍一种最佳设置,该设置不仅使Lighthouse满意,而且还克服了通常在加载字体时令人恐惧的无样式文本(FOUT)。我们将使用原始的HTML,CSS和JavaScript进行所有操作,因此可以将其应用于任何技术堆栈。另外,我们还将介绍Gatsby实施以及我开发的插件,该插件是针对该插件的简单的嵌入式解决方案。

当浏览器加载网站时,它会从DOM(即HTML的对象模型)和CSSOM(即所有CSS选择器的映射)创建渲染树。渲染树是关键渲染路径的一部分,它表示浏览器渲染页面所经过的步骤。为了使浏览器呈现页面,它需要加载并解析HTML文档以及该HTML中链接的每个CSS文件。

您可能会认为字体样式表在文件大小方面很小,因为它们通常最多包含几个@ font-face定义。它们应该不会对渲染产生任何明显的影响,对吧?

假设我们正在从外部CDN加载CSS字体文件。当我们的网站加载时,浏览器需要等待CDN加载该文件并将其包含在呈现树中。不仅如此,它还需要等待CSS @ font-face定义中作为URL值引用的字体文件被请求并加载。

底线:字体文件成为关键渲染路径的一部分,并增加了页面渲染延迟。

对于普通用户来说,任何网站最重要的部分是什么?当然是内容。这就是为什么在网站加载过程中需要尽快向用户显示内容的原因。为此,关键渲染路径需要减少到关键资源(例如HTML和关键CSS),并在渲染页面后加载所有其他内容,包括字体。

如果用户通过缓慢,不可靠的连接浏览未优化的网站,他们将坐在空白屏幕上等待字体文件和其他重要资源加载完成,这会让他们感到恼火。结果?除非该用户非常耐心,否则他们可能会放弃并关闭窗口,以为该页面根本没有加载。

但是,如果推迟了非关键性资源并尽快显示了内容,则用户将能够浏览网站并忽略任何缺少的表示样式(如字体),也就是说,如果它们没有进入内容的方式。

在这里没有必要重新发明轮子。哈里·罗伯茨(Harry Roberts)已经做了出色的工作,描述了一种加载Web字体的最佳方法。他通过Google字体的深入研究和数据深入研究了细节,将其分解为四个步骤:

用JavaScript渲染内容后,异步加载字体样式表和字体文件。

注意字体样式表链接上的media =“ print”。浏览器会自动为打印样式表赋予低优先级,并将其排除在关键渲染路径之外。加载打印样式表后,将触发onload事件,将媒体切换为默认的所有值,并将字体应用于所有媒体类型(屏幕,打印和语音)。

需要特别注意的是,自托管字体还可以帮助解决渲染阻止问题,但这并非总是一种选择。例如,使用CDN可能是不可避免的。在某些情况下,让CDN在提供静态资源方面承担繁重的工作是有好处的。

即使我们现在以最佳的非渲染阻止方式加载字体样式表和字体文件,我们还是引入了一个小的UX问题…

为什么会这样呢?为了消除渲染阻止资源,我们必须在页面内容渲染后(即显示在屏幕上)加载它。对于在关键资源之后异步加载的低优先级字体样式表,用户可以看到字体从后备字体变为下载字体的瞬间。不仅如此,页面布局可能会移位,从而导致某些元素在加载Web字体之前看起来不完整。

处理FOUT的最好方法是使后备字体和Web字体之间的过渡平滑。为此,我们需要:

选择与异步加载的字体尽可能接近的合适的后备系统字体。

再次调整后备字体的字体样式(字体大小,行高,字母间距等),以使其与异步加载的字体的特征再次尽可能接近。

呈现异步加载的字体文件后,清除后备字体的样式,然后应用打算用于新加载的字体的样式。

我们可以使用字体样式匹配器找到最佳的后备系统字体,并为计划使用的任何给定Web字体配置它们。准备好后备字体和Web字体的样式后,我们可以继续下一步。

我们可以使用CSS字体加载API来检测Web字体何时加载。为什么? Typekit的网络字体加载器曾经是实现它的一种比较流行的方法,尽管它很想继续使用它或类似的库,但我们需要考虑以下几点:

它已经四年没有更新了,这意味着如果插件方面有任何问题或需要新功能,则很可能没人会实施和维护它们。

我们已经使用Harry Roberts的代码段有效地处理了异步加载,并且不需要依赖JavaScript来加载字体。

如果您问我,对于像这样的简单任务,使用类似于Typekit的库就太多了。我想避免使用任何第三方库和依赖项,因此让我们自己实施解决方案,并尝试使其尽可能简单明了,而不会过度设计。

尽管CSS字体加载API被认为是实验性技术,但它具有大约95%的浏览器支持。但是无论如何,如果将来API发生更改或不推荐使用,我们都应该提供备用。丢失字体的风险是不值得的。

CSS字体加载API可用于动态和异步加载字体。我们已经决定不依赖JavaScript来完成诸如字体加载之类的简单操作,并且已使用带有预加载和预连接功能的纯HTML的最佳方式解决了该问题。我们将使用API​​中的一个函数,该函数将帮助我们检查字体是否已加载并可用。

body:not(.wf-merriweather--loaded):not(.no-js){́ font-family:[fallback-system-font]; / *后备字体样式* /}。wf-merriweather--loaded,.no-js {́ font-family:“ [web-font-name]”; / * Webfont样式* /} / *可访问的隐藏* /。hidden {位置:绝对;溢出:隐藏;剪辑:rect(0 0 0 0);高度:1px;宽度:1px;边距:-1px;填充:0;边界:0; }

JavaScript是神奇的地方。如前所述,我们正在使用CSS字体加载API的check()函数检查字体是否已加载。同样,字体大小参数可以是任何值(以像素为单位);需要与我们正在加载的字体名称匹配的字体系列值。

var interval = null;函数fontLoadListener(){var hasLoaded = false;尝试{has hased = document.fonts.check('12px“ [web-font-name]”'))} catch(错误){console.info(“ CSS字体加载API错误”,错误); fontLoadedSuccess();返回; } if(hasLoaded){fontLoadedSuccess(); }}函数fontLoadedSuccess(){if(interval){clearInterval(interval); } / *应用类名* /} interval = setInterval(fontLoadListener,500);

这里发生的事情是我们使用fontLoadListener()设置监听器,该监听器定期运行。此功能应尽可能简单,以便在间隔内有效运行。我们正在使用try-catch块来处理任何错误并捕获任何问题,以便在JavaScript错误的情况下仍可以使用网络字体样式,从而使用户不会遇到任何UI问题。

接下来,我们使用fontLoadedSuccess()来说明何时成功加载字体。我们需要确保先清除间隔,以免不必要地在间隔之后执行检查。在这里,我们可以添加应用网络字体样式所需的类名。

最后,我们开始间隔。在此示例中,我们将其设置为最多500毫秒,因此该功能每秒运行两次。

与传统的Web开发(甚至是常规的create-react-app技术堆栈)相比,Gatsby所做的事情有所不同,这使得实现此处介绍的内容变得有些棘手。

为简化此操作,我们将开发一个本地的Gatsby插件,因此在下面的示例中,所有与我们的字体加载器相关的代码都位于plugins / gatsby-font-loader中。

我们的字体加载器代码和配置将分为三个主要的Gatsby文件:

插件配置(gatsby-config.js):我们将在项目中包括本地插件,列出所有本地和外部字体及其属性(包括字体名称和CSS文件URL),并包括所有预连接URL。

服务器端代码(gatsby-ssr.js):我们将使用Gatsby API中的setHeadComponents函数,使用配置在HTML 中生成并包含预加载和预连接标签。然后,我们将生成HTML片段,以隐藏字体并将其包含在使用setPostBodyComponents的HTML中。

客户端代码(gatsby-browser.js):由于此代码在页面加载后和React启动后运行,因此它已经是异步的。这意味着我们可以使用react-helmet注入字体样式表链接。我们还将启动字体加载侦听器来处理FOUT。

我知道,有些东西很复杂。如果您只想为性能,异步字体加载和FOUT破坏提供简单的嵌入式解决方案,那么我已经为此专门开发了gatsby-omni-font-loader插件。它使用本文中的代码,我正在积极维护它。如果您有任何建议,错误报告或代码贡献,请随时在GitHub上提交。

内容可能是用户在网站上体验的最重要组成部分。我们需要确保内容获得最高优先级并尽快加载。这意味着在加载过程中使用最低限度的展示样式(即内嵌的关键CSS)。这也是为什么在大多数情况下将网络字体视为非关键字体的原因-用户仍然可以在不使用字体的情况下使用内容-因此,在页面呈现后加载它们非常好。

但这可能会导致FOUT和布局偏移,因此需要字体加载侦听器才能在后备系统字体和Web字体之间进行平滑切换。

我想听听您的想法!在评论中让我知道您如何处理项目中Web字体加载,渲染阻止资源和FOUT的问题。