专栏首页WecTeam2020前端性能优化清单(五)

2020前端性能优化清单(五)

传输优化

39. 您是否对所有的 JavaScript 库进行了异步加载?

当用户请求一个页面时,浏览器获取 HTML 构造 DOM,获取 CSS 构造 CSSOM,然后通过匹配 DOM 和 CSSOM 生成一个渲染树。只要需要解析 JavaScript 时,浏览器就会延迟开始渲染页面的时间。作为开发人员,我们必须明确地告诉浏览器立即开始渲染页面。可以通过给脚本添加 HTML 中的 deferasync 属性。

而实际证明,我们应该选用 defer,慎用 async( 代价是 IE 9 及 9 以下版本中运行的脚本可能会被破坏,进而影响到用户访问[2])。根据 Steve Souders 的说法,使用 async 的脚本一旦获取到,就会立即执行。如果这种情况发生得非常快,例如当脚本处于缓存就绪状态时,它实际上会阻塞 HTML 解析器。使用 defer,浏览器在解析 HTML 之前不会执行脚本。因此,除非在开始渲染之前需要执行 JavaScript,否则最好都使用 defer。

另外,如上所述,要限制第三方库和脚本的影响,特别是要注意社交分享按钮和 iframe 标签(如地图)的使用。size-limit 库[3]有助于防止 JavaScript 库膨胀:如果您不小心添加了一个大的依赖项,该工具将通知您并抛出一个错误。您可以使用静态社交分享按钮[4](例如 SSBG[5])和静态的交互式地图链接[6]作为替代。

您可能还需要修改非阻塞脚本加载程序以符合 CSP[7]

40. 使用 IntersectionObserver 和优先级提示(priority hints)懒加载耗性能的组件

通常,我们应该延迟加载所有耗性能的组件,比如大的 JavaScript、视频、iframe、小组件和潜在的要加载的图片。Native lazy-loading[8] 可以帮助我们延迟加载图片和 iframe,我们可以在 <script>, <img>, 或者 <link>(只有 Blink 有效) 元素上使用 important 属性(high 或者 low)。这是一个将图片置于次要地位,以及重新确定脚本优先级的很好的方法。然而,有时我们可能需要更细粒度的控制。

执行延迟加载脚本的最有效方式是使用 Intersection Observer API[9],该 API 可以异步观察目标元素与祖先元素或文档的 viewport 之间交集的变化。大致上,您需要创建一个 IntersectionObserver 对象,它接收一个回调函数和一组参数。然后我们添加一个观察目标。

当目标变得可见或不可见时,回调函数就会执行,所以当它和 viewport 相交时,您可以在元素变得可见之前执行一些操作。实际上,我们可以通过 rootMargin(围绕根的边距)和 threshold (一个数字或一组数字,表示目标的可见性的百分比)对何时调用观察者的回调进行细粒度控制。

Alejandro Garcia Anglada 发布了一个关于如何实现它的简易教程[10],Rahul Nanwani 写了一篇详细的关于延迟加载前景和背景图片的文章[11],谷歌基础也提供了一个关于延迟加载图片和视频的详细教程[12]。还记得使用移动和固定物体,以艺术为导向来引导长篇故事阅读吗?你也可以用 Intersection Observer API 实现高性能长篇故事阅读[13]

另外,请注意,特性策略:LazyLoad[14] 将提供一种机制,允许我们基于某些域强制选择 LazyLoad 功能(类似于内容安全策略[15]的工作方式)。

请再次检查所有可以延迟加载的内容,即使是延迟加载翻译字符串和表情符号。移动 Twitter 就是通过该方法在新的国际化管道中实现了 80%的 JavaScript 执行速度提升。

通过延迟加载翻译字符串,移动 Twitter 在新的国际化管道中实现了 80%的 JavaScript 执行速度提升。(图片来源: Addy Osmani)

41. 渐进加载图片

您甚至可以通过在页面中使用渐进式图片加载[16]将延迟加载效果提升到新的级别。与 Facebook、Pinterest 和 Medium 类似,您可以先加载低质量甚至模糊的图片,然后随着页面继续加载,使用 Guy Podjarny 提出的 LQIP(低质量图片占位符)技术[17]将它们替换为高质量的完整版本。

关于这些技术是否改善了用户体验,众说不一,但它确实提高了首次进行有意义绘制的时间。我们甚至可以通过使用 SQIP[18] 创建一个低质量的图片版本作为 SVG 占位符,或者使用 CSS 线性渐变作为渐变图片占位符[19])来自动实现。这些占位符可以嵌入到 HTML 中,因为它们可以很好地使用文本压缩方法进行压缩。Dean Hume 在他的文章[20]中描述了如何使用 Intersection Observer 来实现这种技术。

浏览器支持吗?Chrome、Firefox、Edge 的支持不错,Samsung Internet 也还可以。WebKit 预览版也已支持。降级方案如何?如果浏览器不支持 Intersection Observer,我们仍然可以延迟加载[21]一个 pollfill[22] 或立即加载图片。已经有了一个现成的库[23]

想变得更前沿吗?您可以跟踪图像[24]并使用基本形状和边缘创建一个轻量级的 SVG 占位符,首先加载它,然后从占位符矢量图像转换为(加载的)位图图像。

José M. Pérez 实现的 SVG 懒加载技术

42. 您发送了关键 CSS 吗?

为了确保浏览器尽快开始渲染页面,收集渲染首屏可见部分所需的所有 CSS(称为"关键 CSS "或“首屏显示 CSS ”),并将它内联在页面的 <head> 标签中,从而减少往返请求传输,这已经成为了一种通用做法[25]。由于在慢启动阶段交换的包的大小有限,关键 CSS 的预算大约为 14KB。(这个特定的限制不适用于 TCP BBR,但优先处理关键资源并尽早加载它们仍然很重要)。

如果超出这个限制范围,浏览器将需要额外的传输往返以获取更多样式。CriticalCSS[26]Critical[27] 能够帮助我们找到关键 CSS。您可能需要将此应用到每个模板上。

你可以使用 critters Webpack 插件[28]嵌入关键的 CSS 和延迟加载其余的 CSS。如果可能,可以考虑使用 Filament 团队的条件内联方法[29],或者动态地将内联代码转换为静态资源[30]

如果您使用 loadCSS[31] 之类的库异步加载整个 CSS,则没有必要这样做。在 media="print"link 中,您可以欺骗浏览器异步获取 CSS[32],但是一旦它加载完成,就会应用到屏幕环境中。

使用 HTTP/2,可以将关键 CSS 存储在一个单独的 CSS 文件中,并通过服务器推送[33]传输,这样就不会使 HTML 变得膨胀。但问题是服务器推送很麻烦,浏览器之间有很多陷阱和竞争条件。它没有被统一支持,并且有一些缓存问题(参见 Hooman Beheshti 演讲的第 114 页)。实际上,这种影响可能是负面的[34],会使网络缓冲区膨胀,阻止文档中真正的数据帧的传输。此外,由于 TCP 启动缓慢,服务器推送在热连接上会更有效[35]

即使使用 HTTP/1,将关键 CSS(和其他重要资源[36])放在根域中的单独文件中也是有好处[37]的,因为有缓存,它有时甚至会比内联更有用。当请求页面时,Chrome 会基于推测开启第二个到根域的 HTTP 连接,这样就不需要 TCP 连接来获取这个 CSS 了。

需要注意的几个问题是:您只能从您自己的域或授权的域推送资源,而不能像 preload 那样可以从任何域触发 preload。它可以在服务器从客户端获得第一个请求时立即启动。服务器推送的资源会驻留在推送缓存中,并在连接终止时被删除。但是,由于 HTTP/2 连接可以跨多个选项卡重用,所以来自其他选项卡的请求也可以声明已推送的资源。但是我们还不能完全信任它[38],尤其是在 Safari 和 Edge 中。

目前,服务器还没有一种简单的方法得知被推送的资源是否已经在用户的某个缓存中[39],因此每次用户访问时,资源都会被继续推送。所以,您可能需要创建一个缓存感知的 HTTP/2 服务器推送机制[40]。如果被获取,您可以尝试根据缓存中已经存在的内容的索引从缓存中获取它们,从而避免服务器的二次推送。

有一段时间,缓存摘要规范[41]被认为可以免除手动构建"缓存感知"服务器的需要,大体上,我们只需要在 HTTP/2 中声明一个新的框架类型来通知该主机名的缓存中已经存在的内容。但是,cache-digest 规范后来被抛弃了[42],所以目前仍然还没有解决方案。

对于动态内容,当服务器需要一些时间来生成响应时,浏览器在这段时间是不能发出任何请求的,因为无法确定页面可能引用到的任何子资源。对于这种情况,我们可以预热连接并增加 TCP 拥塞窗口的大小,以便将来的请求可以更快地完成。而且,所有内联资源通常都是非常适合服务器推送的对象。事实上,Inian Parameshwaran 做了一项出色的研究,比较了 HTTP/2 Push 和 HTTP Preload[43],这是一本非常棒的读物,包含了您可能需要的所有细节。

结论:Sam Saccone 指出[44]preload 有利于使资源的开始下载时间接近最初的请求时间,而服务器推送适合切割一个完整的 RTT(或者更多[45] — 如果你有一个 service worker 用来防止不必要的推送。

43. 尝试重新组合您的 CSS 规则

我们已经熟悉了上述关键 CSS 方法,但是还有一些优化可以帮助我们做得更好。哈里·罗伯茨进行了一项了不起的研究,得出了相当惊人的结果[46]。例如,按照媒体查询条件把主 CSS 文件进行拆分可能是一个不错的改进。这样,浏览器将使用高优先级检索关键 CSS,使用低优先级处理(脱离关键路径)其他的所有内容。

另外,避免将 <link rel="stylesheet"> 放在 async 代码段之前。如果脚本不依赖于样式表,可以考虑将阻塞脚本置于阻塞样式之上。如果存在依赖,可以将 JavaScript 分成两部分,将它们分别放到 CSS 的两边来加载。

Scott Jehl 解决了另一个有趣的问题,他使用 service worker 缓存了一个内联的 CSS 文件[47],如果您正在使用关键 CSS 方法,这将是一个常见的问题。一般而言,为了使用 JavaScript 快速查找到 CSS,我们需要添加一个 ID 属性到 style 元素上,然后 JavaScript 可以使用缓存 API 来将其存储在本地浏览器缓存(内容格式为 text/css)中,以用于随后的页面。为了避免在后续页面上进行内联,从外部引用缓存的资源,我们在第一次访问一个站点时设置了一个 cookie。

值得注意的是,动态样式也可能导致很高的代价[48],但通常仅在依赖于数百个并发渲染的组合组件时才会出现这种情况。因此,如果您正在使用 CSS-in-JS,请确保您的 CSS-in-JS 库在您的 CSS 不依赖于主题或属性时能优化执行,也请不要过度组合样式组件。Aggelos Arvanitakis 分享了更多关于 CSS-in-JS 性能成本的见解[49]

44. 您会流式化响应吗?

数据流(Streams)[50]常常被遗忘和忽略,它提供了一个接口来读写异步数据块,即使在给定的时间内只有其中的一个子集在内存中可用。大体上,它们允许发出原始请求的页面在第一块数据可用时立即开始处理响应,并使用流式优化解析器逐步呈现内容。

我们可以从多个源创建一个流。例如,让 service worker 构造一个流,其中 shell 来自缓存,而主体来自网络,而不是提供一个空的 UI shell 并让 JavaScript 填充它。正如 Jeff Posnick 所指出的,如果您的 web 应用程序由 CMS 提供支持,服务器通过拼接部分模板来渲染 HTML,那么该模型将直接转换为使用流响应,模板逻辑会被复制到 service worker(而不是服务器)中。Jake Archibald 的文章 The Year of Web Streams[51] 强调了如何准确地构建它。这样带来的性能提升是相当明显的[52]

数据流化整个 HTML 响应的一个重要优点是,在初始导航请求期间呈现的 HTML 可以充分利用浏览器的流化 HTML 解析器。加载页面后插入到文档中的 HTML 块(常见的通过 JavaScript 填充内容的情况)不能利用这种优化。

浏览器支持吗?在 Chrome、Firefox、Safari 和 Edge 中可以对该 API 提供部分支持[53]在所有现代浏览器中都支持 service worker[54]

45. 考虑让组件具有可连接性

数据可能是耗性能[55]的,随着负载的增长,我们需要尊重那些在访问我们的网站或应用程序时选择节省数据的用户。[数据保存客户端提示请求头](Save-Data client hint request header)(https://developers.google.com/web/updates/2016/02/save-data)允许我们自定义应用程序和负载,以满足成本和性能受限的用户。事实上,你可以将高分辨率图像的请求重写为低分辨率图像[56],删除 web 字体,添加视差效果,预览缩略图和无限滚动,关闭视频自动播放、服务器推送,减少显示项的数量,降低图像质量,甚至改变传输标记[57]的方式。Tim Vereecke 发表了一篇非常详细的关于数据保存策略[58]的文章。

目前只有 Chrome、Android 版 Chrome 或安装了 Data Saver 扩展的桌面设备上支持。最后,您还可以使用网络信息 API[59] 来传送基于网络类型的低/高分辨率图像[60]和视频。网络信息 API,特别是 navigator.connection.effectiveType,使用了 RTTdownlinkeffectiveType 值(以及其他[61])为用户提供了可以处理的连接和数据。

在此背景下,Max Stoiber 提出了连接感知组件,Addy Osmani 提出了自适应模块服务。例如,使用 React,我们可以针对不同连接类型编写不同的组件用于渲染。正如 Max 所建议的,新闻文章中的一个 <Media /> 组件可能输出:

  • 离线:一个带有 alt 属性的占位符
  • 2G /保存数据模式:低分辨率图像
  • 非视网膜屏幕上的 3G:中分辨率图像
  • 视网膜上的 3G:高分辨率的视网膜图像
  • 4G:高清视频

Dean Hume 使用 service worker 提供了一个类似逻辑的实现[62]。对于一个视频,我们可以默认显示一个视频海报,然后在更好的连接状态下显示 “Play” 图标以及视频播放器外壳、视频元数据等。对于不支持的浏览器,我们可以监听 canplaythrough 事件[63]并使用 Promise.race() 来终止源加载,如果 canplaythrough 事件在 2 秒内还没有触发。

如果你想再深入一点,这里有一些资源:

  • Addy Osmani 演示了如何在 React 中实现自适应服务[64]
  • React 自适应加载钩子和工具[65]为 React 实现提供了代码片段
  • Netanel Basel 探索了 Angular 中的连接感知组件[66]
  • Theodore Vorilas 分享了如何使用 Vue 中的网络信息 API 为自适应组件提供服务[67]

46. 考虑让你的组件对设备内存敏感

然而,网络连接只给了我们一个用户维度的视角。更进一步的,您还可以使用设备内存 API[68]根据可用的设备内存动态调整资源[69]navigator.deviceMemory 返回设备的内存大小,以 gb 为单位,四舍五入到最接近的 2 的幂。该 API 还具有一个客户端提示头,即 Device-Memory,它可以得到相同的值。

好处:Umar Hansa 展示了如何根据设备内存、网络连接和硬件并发性[70]做动态导入,延迟加载耗性能的脚本[71],改善体验。

DevTools 中的“优先级”队列。图片来源:Ben Schwarz, The Critical Request

47. 预热连接以加速传输

使用 resource hint[72] 节省时间:dns-prefetch 可以在后台执行 DNS 查找,preconnect 控制浏览器在后台启动连接握手(DNS, TCP, TLS),prefetch 要求浏览器请求一个资源,preload 预加载资源且不执行。

还记得 prerender 吗?它提示浏览器在后台为下一个导航构建整个页面的资源。实现中的问题是相当棘手的[73],从巨大的内存占用和带宽使用到多个注册的分析点击率和广告曝光量等,都很有挑战。

不出所料,它被弃用了,但 Chrome 团队基于此提出了 NoState Prefetch mechanism[74]。事实上,Chrome 已经把 prerender 当作了一个 NoState Prefetch,所以我们今天就可以使用它。正如 Katie Hempenius 在文章中解释的那样,"就像 prerender 一样,NoState Prefetch 会提前获取资源;但不同的是,它不执行 JavaScript,也不提前渲染页面的任何部分。" NoState Prefetch 只使用约 45MiB 的内存,且子资源处理的优先级只是 IDLE 级别。从 Chrome 69 开始,NoState Prefetch 就在所有的请求中加入了请求头,以使它们区别于普通的浏览。

实际中,我们至少会使用 preconnect 和 dns-prefetch,会谨慎使用 prefetch, preload 和 prerender。注意,只有在您确信用户下一步将需要什么资源(例如,在一个采购漏斗中)时,才应该使用前者。

请注意,即使使用了 preconnect 和 dns-prefetch,浏览器也会限制并行查找/连接的主机数量,所以,根据优先级对它们进行排序会更安全。

使用 resource hint 可能是提高性能的最简单的方法,而且它确实工作得很好[75]。什么时候使用?正如 Addy Osmani 所解释的[76],可以将我们高度信任的资源预加载到当前页面。Prefetch 资源可能在用于未来访问到的多个导航页面,例如用户尚未访问的页面所需的 Webpack 包。

Addy 关于“Chrome 中的加载优先级[77]”的文章展示了 Chrome 如何准确地解析 resource hint,一旦你决定了哪些资源对渲染是至关重要的,你就可以为它们分配高优先级。要查看请求的优先级,可以在 Chrome DevTools 网络请求表(以及 Safari)中启用“优先级”选项。

图片来源:Pat Meenan

由于字体通常是页面上的重要资源,有时通过 preload 请求浏览器下载重要字体是一个很好的方式。然而,需要仔细检查它是否真的有助于性能,因为在预加载字体时存在一个优先级的难题[78]:由于预加载被视为非常重要,它可以跳过甚至更关键的资源,如关键 CSS。

您还可以动态加载 JavaScript[79],有效地延迟加载执行。此外,由于 <link rel="preload"> 接受 media 属性,所以可以根据 @media 查询规则有选择地对资源进行优先级排序[80]

需要记住的几个问题[81]preload 有助于将资源的开始下载时间[82]提前到更接近初始请求的时间,但是预加载资源会占用与发出请求的页面相关的内存缓存。preload 可以很好地处理 HTTP 缓存:如果资源已经在 HTTP 缓存中,则永远不会发送网络请求。

因此,preload 对于后续触发加载的资源,如 background-image 加载的图片、内联关键的 CSS(或 JavaScript)并预加载其余的 CSS(或 JavaScript)非常有用。此外,只有在浏览器从服务器接收到 HTML 并且解析器找到 preload 标记之后,preload 标记才能初始化预加载。

通过 HTTP 头文件进行预加载可能会快一些,因为我们不需要等待浏览器解析 HTML 来启动请求(这是有争议的)。Early Hints[83] 甚至会更有帮助,在发送 HTML 的响应头(在 Chromium 和 Firefox 的 roadmap 上)之前,就可以启用 preload。另外,Priority Hints[84] 将帮助我们标示脚本的加载优先级。

注意:如果您正在使用 preload,必须定义 as,否则不会加载;不带 crossorigin 属性的预加载字体会被重复获取。

48. 使用 service worker 做缓存和网络降级

在网络上,没有比用户机器上本地存储的缓存更快的了。如果您的网站运行的是 HTTPS,请使用“实用主义者的 service worker 指南[85]”,将静态资源缓存到 service worker 中,并存储脱机降级资源(甚至脱机页面),然后从用户的机器中检索它们,而不是发起网络请求。另外,查看一下 Jake 的离线指南[86]和免费的 Udacity 课程“离线 Web 应用程序[87]”。

浏览器支持吗?如上所述,它得到了广泛的支持[88],而网络是它的后盾。它是否有助于提高性能?是的[89]。而且它变得越来越好,例如使用 Background Fetch 可以允许后台从 service worker 进行上传/下载。

Service worker 有许多用途。例如,您可以实现“保存为离线”功能[90]处理损坏的图像[91]在选项卡之间引入消息传递[92],或者根据请求类型提供不同的缓存策略[93]。通常,一种常见的可靠策略是将应用程序 shell 与几个关键页面一起存储在 service worker 的缓存中,比如离线页面、首页和其他重要页面。

不过,有几个问题需要记住。有了 service worker 之后,我们需要注意 Safari 中的 range 请求[94](如果您为 service worker 使用了 Workbox,它有一个 range 请求模块)。如果您遇到了 DOMException: Quota exceed,浏览器控制台中出现错误,可以查看 Gerardo 的文章当 7KB 等于 7MB[95]

Gerardo 写道,“如果你正在构建一个渐进式的 web 应用程序,使用 service worker 从 CDN 缓存静态资产,你可能会面对臃肿的缓存存储,请确保跨源的资源都设置了合适的 CORS 响应头[96], service worker 不会缓存不透明的响应[97],你可以通过给 <img> 设置 crossorigin 属性将图像资源设置为 CORS 模式[98]。”

使用 service worker 的一个很好的起点是 Workbox,它是一组专门为构建渐进式 web 应用程序而构建的 service worker 库。

49. 您是否在 CDN/Edge 上使用了 service worker,例如用于 A/B 测试?

现在,我们已经非常习惯在客户端上运行 service worker,但是在 CDN 服务器上实现它们[99],也可以帮助调整性能。

例如,在 A/B 测试中,当 HTML 需要为不同的用户改变其内容时,我们可以使用 CDN 服务器上的 service worker[100] 来处理逻辑。我们也可以数据流化 HTML 重写[101],以加快使用了谷歌字体的网站。

Service worker 的安装率曲线。根据 Web 年鉴,只有 0.44%的桌面页面注册了 service worker,。

50. 优化渲染性能

使用 CSS 容器[102]隔离耗性能的组件 — 例如,限制浏览器样式的作用域,如用于画布外导航的布局和绘制工作,或者第三方小组件。确保在滚动页面或元素展示动画效果时没有延迟,能始终达到每秒 60 帧。至少也要使每秒帧数在 60 到 15 的混合范围内。使用 CSS 的 will-change[103] 通知浏览器哪些元素和属性将会改变。

另外,度量运行时渲染性能[104](例如,在 DevTools 中[105])。首先,请参阅 Paul Lewis 关于浏览器渲染优化的免费 Udacity 课程[106],以及 Georgy Marchuk 关于浏览器绘图和 web 性能思考的文章[107]

如果你想更深入地研究这个话题,Nolan Lawson 在他的文章中分享了一些精确测量布局性能的技巧[108],Jason Miller 也提出了一些可供选择的技术。我们也有一篇由 Sergey Chikuyonok 写的关于如何正确使用 GPU 动画[109]的文章。

注意:对 GPU 合成层的更改是最容易的[110],可以通过 opacitytransform 来触发合成。Anna Migas 在她关于调试 UI 渲染性能[111]的演讲中也提供了很多实用的建议。

51. 你优化过渲染体验吗?

虽然组件在页面上的显示顺序以及如何向浏览器提供资源的策略很重要,但是我们也不应该低估感知性能[112]的作用。这个概念涉及到等待的心理,基本思路是在其他事情发生时让顾客保持忙碌或投入。这就是感知管理[113]抢先开始[114]提前完成[115]容忍管理[116]的作用。

这意味着什么?在加载资源时,我们可以试着总是走在客户的前面一步,这样虽然后台处理了很多事情,但用户体验仍然迅速。为了保持客户的关注,我们可以尝试骨架屏幕(实现演示[117]),而不是展示加载中的一个指示器,添加过渡/动画,并在没有更多优化的情况下欺骗用户体验[118]。但是要注意:在部署之前应该对骨架屏幕进行测试,因为一些测试显示,从所有指标来看,骨架屏幕的性能是最差的[119]

52. 你是否防止了布局改变和重新绘制?

在可感知的性能领域中,其中一种更具破坏性的体验可能是布局转移,或者说是回流,这是由重新调整的图像和视频、web 字体、注入的广告或异步加载的用实际内容填充组件的脚本引起的。因此,客户开始阅读一篇文章时,可能会被阅读区域上方的布局中断。这种经历通常是突然的,而且相当令人迷惑:这可能是加载优先级需要重新考虑的情况。

社区已经开发了一些技术和变通方法来避免回流。应该始终在图像上设置宽度和高度属性[120],现代浏览器在默认情况下会分配框并保留空间(Firefox, Chrome)。

对于图像或视频,我们都可以使用 SVG 占位符[121]来保留媒体将出现在其中的显示框。这意味着当您需要保持它的长宽比时,该区域将被适当地保留。

考虑使用本地延迟加载[122],而不是使用带有外部脚本的延迟加载,或者只在本地延迟加载不受支持的情况下使用混合延迟加载[123]

如上所述,始终要将 web 字体重绘分组[124],一次性从所有降级字体转换为 web 字体—只需确保这种转换不会太突然,通过使用字体样式匹配器[125]调整字体之间的行高和间距即可。(请注意,调整是复杂的,会形成复杂的字体堆栈)。

使用 Layout Instability API[126]测量布局稳定性,可以确保涵盖了回流的影响。使用它,您可以计算累积布局移位[127](CLS)分数,并将其作为测试中的一个需求引入,这样,每当出现一个回归,您都可以跟踪并修复它。

为了计算布局的移位分数,浏览器查看两个渲染帧之间的视口大小和视口中不稳定元素的移动。理想情况下,比分应该接近 0。Milica Mihajlija 和 Philip Walton 有一本关于 CLS 是什么以及如何衡量它的伟大指南[128]。这是一个很好的起点,可以度量和维护可感知的性能,避免中断,特别是对于业务关键型任务。

额外的好处:如果你想要减少重绘[129],查一下 Charis Theodoulou 的最小化 DOM 重绘和布局的指南,还有 Paul Irish 的强制布局和重绘的列表,还有 CSSTriggers.com,一个关于那些样式会触发布局、重绘和合成的 CSS 属性参考表。

参考资料

[1]

https://www.smashingmagazine.com/2020/01/front-end-performance-checklist-2020-pdf-pages: https://www.smashingmagazine.com/2020/01/front-end-performance-checklist-2020-pdf-pages

[2]

代价是 IE 9 及 9 以下版本中运行的脚本可能会被破坏,进而影响到用户访问: https://github.com/h5bp/lazyweb-requests/issues/42

[3]

size-limit 库: https://github.com/ai/size-limit

[4]

静态社交分享按钮: https://www.savjee.be/2015/01/Creating-static-social-share-buttons/

[5]

SSBG: https://simplesharingbuttons.com/

[6]

静态的交互式地图链接: https://developers.google.com/maps/documentation/static-maps/intro

[7]

修改非阻塞脚本加载程序以符合 CSP: https://calendar.perfplanet.com/2018/a-csp-compliant-non-blocking-script-loader/

[8]

Native lazy-loading: https://web.dev/native-lazy-loading/

[9]

Intersection Observer API: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API

[10]

简易教程: https://medium.com/@aganglada/intersection-observer-in-action-efc118062366

[11]

延迟加载前景和背景图片的文章: https://css-tricks.com/the-complete-guide-to-lazy-loading-images/

[12]

详细教程: https://developers.google.com/web/fundamentals/performance/lazy-loading-guidance/images-and-video/

[13]

高性能长篇故事阅读: https://github.com/russellgoldenberg/scrollama

[14]

特性策略:LazyLoad: https://www.chromestatus.com/feature/5641405942726656

[15]

内容安全策略: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

[16]

渐进式图片加载: https://calendar.perfplanet.com/2017/progressive-image-loading-using-intersection-observer-and-sqip/

[17]

LQIP(低质量图片占位符)技术: https://www.guypo.com/introducing-lqip-low-quality-image-placeholders

[18]

SQIP: https://github.com/technopagan/sqip

[19]

渐变图片占位符: https://calendar.perfplanet.com/2018/gradient-image-placeholders/

[20]

他的文章: https://calendar.perfplanet.com/2017/progressive-image-loading-using-intersection-observer-and-sqip/

[21]

延迟加载: https://medium.com/@aganglada/intersection-observer-in-action-efc118062366

[22]

pollfill: https://github.com/jeremenichelli/intersection-observer-polyfill

[23]

库: https://github.com/ApoorvSaxena/lozad.js

[24]

跟踪图像: https://jmperezperez.com/svg-placeholders/

[25]

通用做法: https://www.smashingmagazine.com/2015/08/understanding-critical-css/

[26]

CriticalCSS: https://github.com/filamentgroup/criticalCSS

[27]

Critical: https://github.com/addyosmani/critical

[28]

critters Webpack 插件: https://github.com/GoogleChromeLabs/critters

[29]

条件内联方法: https://www.filamentgroup.com/lab/modernizing-delivery.html

[30]

动态地将内联代码转换为静态资源: https://www.smashingmagazine.com/2018/11/pitfalls-automatically-inlined-code/

[31]

loadCSS: https://github.com/filamentgroup/loadCSS

[32]

欺骗浏览器异步获取 CSS: https://www.filamentgroup.com/lab/load-css-simpler/

[33]

服务器推送: https://www.filamentgroup.com/lab/modernizing-delivery.html

[34]

负面的: https://jakearchibald.com/2017/h2-push-tougher-than-i-thought/

[35]

服务器推送在热连接上会更有效: https://docs.google.com/document/d/1K0NykTXBbbbTlv60t5MyJvXjqKGsCVNYHyLEXIxYMv0/edit

[36]

其他重要资源: https://csswizardry.com/2019/05/self-host-your-static-assets/

[37]

好处: http://www.jonathanklein.net/2014/02/revisiting-cookieless-domain.html

[38]

还不能完全信任它: https://jakearchibald.com/2017/h2-push-tougher-than-i-thought/#multiple-pages-can-use-the-same-http2-connection

[39]

用户的某个缓存中: https://blog.yoav.ws/tale-of-four-caches/

[40]

缓存感知的 HTTP/2 服务器推送机制: https://css-tricks.com/cache-aware-server-push/

[41]

缓存摘要规范: https://calendar.perfplanet.com/2016/cache-digests-http2-server-push/

[42]

cache-digest 规范后来被抛弃了: https://lists.w3.org/Archives/Public/ietf-http-wg/2019JanMar/0033.html

[43]

Inian Parameshwaran 做了一项出色的研究,比较了 HTTP/2 Push 和 HTTP Preload: https://dexecure.com/blog/http2-push-vs-http-preload/

[44]

指出: https://medium.com/@samccone/performance-futures-bundling-281543d9a0d5

[45]

或者更多: https://blog.yoav.ws/being_pushy/,取决于你的服务器响应时间

[46]

相当惊人的结果: https://csswizardry.com/2018/11/css-and-network-performance/

[47]

使用 service worker 缓存了一个内联的 CSS 文件: https://www.filamentgroup.com/lab/inlining-cache.html

[48]

动态样式也可能导致很高的代价: https://calendar.perfplanet.com/2019/the-unseen-performance-costs-of-css-in-js-in-react-apps/

[49]

更多关于 CSS-in-JS 性能成本的见解: https://calendar.perfplanet.com/2019/the-unseen-performance-costs-of-css-in-js-in-react-apps/

[50]

数据流(Streams): https://streams.spec.whatwg.org/

[51]

The Year of Web Streams: https://jakearchibald.com/2016/streams-ftw/

[52]

这样带来的性能提升是相当明显的: https://www.youtube.com/watch?v=Cjo9iq8k-bc

[53]

部分支持: https://caniuse.com/#feat=streams

[54]

在所有现代浏览器中都支持 service worker: https://caniuse.com/#feat=serviceworkers

[55]

耗性能: https://whatdoesmysitecost.com/

[56]

数据保存客户端提示请求头](Save-Data client hint request header)(https://developers.google.com/web/updates/2016/02/save-data)允许我们自定义应用程序和负载,以满足成本和性能受限的用户。事实上,你可以[将高分辨率图像的请求重写为低分辨率图像: https://css-tricks.com/help-users-save-data/

[57]

传输标记: https://dev.to/addyosmani/adaptive-serving-using-javascript-and-the-network-information-api-331p

[58]

数据保存策略: https://calendar.perfplanet.com/2018/data-shaver-strategies/

[59]

网络信息 API: https://googlechrome.github.io/samples/network-information/

[60]

低/高分辨率图像: https://justmarkup.com/log/2017/11/network-based-image-loading/

[61]

其他: https://wicg.github.io/netinfo/

[62]

类似逻辑的实现: https://deanhume.com/dynamic-resources-using-the-network-information-api-and-service-workers/

[63]

监听 canplaythrough 事件: https://benrobertson.io/front-end/lazy-load-connection-speed

[64]

如何在 React 中实现自适应服务: https://www.youtube.com/watch?v=puUPpVrIRkc&t=488s

[65]

React 自适应加载钩子和工具: https://github.com/GoogleChromeLabs/react-adaptive-hooks

[66]

Angular 中的连接感知组件: https://netbasal.com/connection-aware-components-in-angular-3a66bb0bab6f

[67]

如何使用 Vue 中的网络信息 API 为自适应组件提供服务: https://dev.to/vorillaz/serving-adaptive-components-using-the-network-information-api-lbo

[68]

设备内存 API: https://developers.google.com/web/updates/2017/12/device-memory

[69]

根据可用的设备内存动态调整资源: https://calendar.perfplanet.com/2018/dynamic-resources-browser-network-device-memory/

[70]

硬件并发性: https://developer.mozilla.org/en-US/docs/Web/API/NavigatorConcurrentHardware/hardwareConcurrency

[71]

延迟加载耗性能的脚本: https://twitter.com/umaar/status/1206707408506105858

[72]

resource hint: https://w3c.github.io/resource-hints

[73]

相当棘手的: https://vimeo.com/356819848#t=1632s

[74]

NoState Prefetch mechanism: https://developers.google.com/web/updates/2018/07/nostate-prefetch

[75]

工作得很好: https://medium.com/reloading/preload-prefetch-and-priorities-in-chrome-776165961bbf

[76]

正如 Addy Osmani 所解释的: https://medium.com/reloading/preload-prefetch-and-priorities-in-chrome-776165961bbf

[77]

Chrome 中的加载优先级: https://medium.com/reloading/preload-prefetch-and-priorities-in-chrome-776165961bbf

[78]

在预加载字体时存在一个优先级的难题: https://andydavies.me/blog/2019/02/12/preloading-fonts-and-the-puzzle-of-priorities/

[79]

动态加载 JavaScript: https://www.smashingmagazine.com/2016/02/preload-what-is-it-good-for/#dynamic-loading-without-execution

[80]

有选择地对资源进行优先级排序: https://css-tricks.com/the-critical-request/#article-header-id-3

[81]

需要记住的几个问题: https://dexecure.com/blog/http2-push-vs-http-preload/

[82]

资源的开始下载时间: https://www.youtube.com/watch?v=RWLzUnESylc

[83]

Early Hints: https://www.fastly.com/blog/faster-websites-early-priority-hints

[84]

Priority Hints: https://github.com/WICG/priority-hints

[85]

实用主义者的 service worker 指南: https://github.com/lyzadanger/pragmatist-service-worker

[86]

离线指南: https://jakearchibald.com/2014/offline-cookbook/

[87]

离线 Web 应用程序: https://www.udacity.com/course/offline-web-applications--ud899

[88]

广泛的支持: http://caniuse.com/#search=serviceworker

[89]

是的: https://developers.google.com/web/showcase/2016/service-worker-perf

[90]

实现“保存为离线”功能: https://una.im/save-offline/#%F0%9F%92%81

[91]

处理损坏的图像: https://bitsofco.de/handling-broken-images-with-service-worker/

[92]

在选项卡之间引入消息传递: https://www.loxodrome.io/post/tab-state-service-workers/

[93]

根据请求类型提供不同的缓存策略: https://medium.com/dev-channel/service-worker-caching-strategies-based-on-request-types-57411dd7652c

[94]

注意 Safari 中的 range 请求: https://philna.sh/blog/2018/10/23/service-workers-beware-safaris-range-request/

[95]

当 7KB 等于 7MB: https://cloudfour.com/thinks/when-7-kb-equals-7-mb/

[96]

合适的 CORS 响应头: https://cloudfour.com/thinks/when-7-kb-equals-7-mb/#opaque-responses

[97]

不会缓存不透明的响应: https://cloudfour.com/thinks/when-7-kb-equals-7-mb/#should-opaque-responses-be-cached-at-all

[98]

将图像资源设置为 CORS 模式: https://cloudfour.com/thinks/when-7-kb-equals-7-mb/#opt-in-to-cors-mode

[99]

在 CDN 服务器上实现它们: https://blog.cloudflare.com/introducing-cloudflare-workers/

[100]

CDN 服务器上的 service worker: https://www.filamentgroup.com/lab/servers-workers.html

[101]

数据流化 HTML 重写: https://twitter.com/patmeenan/status/1065567680298663937

[102]

CSS 容器: http://caniuse.com/#search=contain

[103]

will-change: http://caniuse.com/#feat=will-change

[104]

运行时渲染性能: https://aerotwist.com/blog/my-performance-audit-workflow/#runtime-performance

[105]

在 DevTools 中: https://developers.google.com/web/tools/chrome-devtools/rendering-tools/

[106]

浏览器渲染优化的免费 Udacity 课程: https://www.udacity.com/course/browser-rendering-optimization--ud860

[107]

浏览器绘图和 web 性能思考的文章: https://css-tricks.com/browser-painting-and-considerations-for-web-performance/

[108]

精确测量布局性能的技巧: https://nolanlawson.com/2018/09/25/accurately-measuring-layout-on-the-web/

[109]

如何正确使用 GPU 动画: https://www.smashingmagazine.com/2016/12/gpu-animation-doing-it-right/

[110]

最容易的: https://blog.algolia.com/performant-web-animations/

[111]

调试 UI 渲染性能: https://vimeo.com/302791098

[112]

感知性能: https://www.smashingmagazine.com/2015/09/why-performance-matters-the-perception-of-time/

[113]

感知管理: https://www.smashingmagazine.com/2015/11/why-performance-matters-part-2-perception-management/

[114]

抢先开始: https://www.smashingmagazine.com/2015/11/why-performance-matters-part-2-perception-management/#preemptive-start

[115]

提前完成: https://www.smashingmagazine.com/2015/11/why-performance-matters-part-2-perception-management/#early-completion

[116]

容忍管理: https://www.smashingmagazine.com/2015/12/performance-matters-part-3-tolerance-management/

[117]

实现演示: https://twitter.com/lukew/status/665288063195594752

[118]

欺骗用户体验: https://blog.stephaniewalter.fr/en/cheating-ux-perceived-performance-and-user-experience/

[119]

骨架屏幕的性能是最差的: https://www.viget.com/articles/a-bone-to-pick-with-skeleton-screens/

[120]

设置宽度和高度属性: https://twitter.com/addyosmani/status/1169813271009886208

[121]

SVG 占位符: https://css-tricks.com/preventing-content-reflow-from-lazy-loaded-images/

[122]

本地延迟加载: https://web.dev/native-lazy-loading/

[123]

混合延迟加载: https://www.smashingmagazine.com/2019/05/hybrid-lazy-loading-progressive-migration-native/

[124]

将 web 字体重绘分组: https://www.zachleat.com/web/comprehensive-webfonts/#fout-class

[125]

字体样式匹配器: https://meowni.ca/font-style-matcher/

[126]

Layout Instability API: https://wicg.github.io/layout-instability/

[127]

累积布局移位: https://web.dev/cls/

[128]

CLS 是什么以及如何衡量它的伟大指南: https://web.dev/cls

[129]

减少重绘: https://medium.com/better-programming/web-performance-dom-reflow-76ac7c4d2d4f

本文分享自微信公众号 - WecTeam(Wec-Team),作者:马雪琴、朱志玉

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-04-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 2020前端性能优化清单(二)

    2015 年,Google推出了[2]Brotli[3],这是一种全新的开源无损数据格式,并被现在所有现代浏览器支持[4]。实际上,Brotli 似乎比 Gzi...

    WecTeam
  • 2020前端性能优化清单(四)

    研究哪些 JavaScript 引擎在你的用户群中占主导地位,然后探索对其进行优化的方法。例如,当针对 Blink 浏览器、Node.js 运行时和 Elect...

    WecTeam
  • 负责任地编写Javascript(二)

    重构工作一开始非常简单,就是到处安装 npm,这其实就是在快速安装生产依赖项,就像一个大学生在做桶支架,而不关心第二天早上的情况一样。

    WecTeam
  • Python+OpenCV 十几行代码模仿世界名画

    现在很多人都喜欢拍照(自拍)。有限的滤镜和装饰玩多了也会腻,所以就有 APP 提供了模仿名画风格的功能,比如 prisma、versa 等,可以把你的照片变成 ...

    Crossin先生
  • React:Table 那些事(1)—— 写在前面

    WEBJ2EE
  • 设置HTTP重定向为HTTPS

    更多关于重定向内容:https://jingyan.baidu.com/article/09ea3ede6bd7c6c0aede3931.html 敲黑板,如...

    林清猫耳
  • 复现经典:《统计学习方法》第21章 PageRank算法

    在实际应用中许多数据都以图(graph)的形式存在,比如,互联网、社交网络都可以看作是一个图。图数据上的机器学习具有理论与应用上的重要意义。pageRank算法...

    黄博的机器学习圈子
  • 你准备好了在云中工作吗?

    无服务器计算,容器化,云原生应用,DevOps,人工智能,机器学习以及混合云和多云解决方案等IT趋势正在成为主流或“新常态”。所有大小企业都在寻找具有许多热门趋...

    张善友
  • 《从零开始学ASP.NET CORE MVC》:为您的机器配置开发环境(二)

    我的是Windows操作系统,因此,我将使用Visual Studio作为.NET Core应用程序开发的编辑器。 当然您可以使用您选择的任何编辑器,不过我推荐...

    角落的白板报
  • 如何成为一名区块链工程师? | 附学习资源

    区块链大本营

扫码关注云+社区

领取腾讯云代金券