优秀网站必须关注的健康指标 | Web Vitals 闪亮登场

Google在五月初提出了一项名为Web Vitals的新计划,旨在根据用户与网页的交互体验感知来评判一个网页的质量,提供各种质量信号的统一指南。并在五月底宣布了一种新的排名算法,如果网页给用户浏览体验很差,网页的搜索排名将会被降权。所以了解web vitals来衡量和优化用户体验是很有必要的。

Core Web Vitals

Core Web Vitals以用户为中心,对页面各个方面进行评分。包括加载时间、交互性和内容加载的稳定性。这些方面体现在下面三个指标上:

  • LCP(Largest Contentful Paint/最大内容绘制):衡量加载性能。 评估页面主要内容可能已完成加载时的感知加载速度。
  • FID(First Input Delay/首次输入延迟):衡量可交互性。评估用户首次尝试与网页交互时的网页响应速度,并量化用户感知体验。
  • CLS(Cumulative Layout Shift/累积布局偏移):衡量视觉稳定性。评估可见页面内容的视觉稳定性,并量化内容的意外布局偏移量。

LCP

如何衡量用户网页主要内容的加载速度,对开发者来说一直都是个关注点。以前的旧的指标(例如load和DOMContentLoaded)并不能很好的体现出来,因为它们和用户屏幕看到的内容不一定相对应。并且以用户为中心的更新性能指标(例如First Contentful Paint(FCP))只能捕捉到加载体验最开始的时候。如果页面有启动动画或者loading,这种时候和用户的体验没有关系。过去的指标(例如FMP和SI)非常复杂,而且通常是错误的。所以有时简单点会更好。根据W3C Web Performance Working Group和Google研究发现,衡量页面何时加载主要内容的一种更准确的方法是看何时渲染最大的那个元素。这种衡量的指标就是LCP。

想要了解这些指标建议背后研究方法的更多信息,可以查看Defining the Core Web Vitals metrics thresholds

考虑的元素

根据Largest Contentful Paint API,LCP考虑的元素类型为:

  • <img>元素
  • <image>元素内的<svg>元素
  • <vedio>元素
  • 通过url()函数加载背景图片的元素(和CSS gradient相反)
  • 包含文本节点或其他内联级文本元素子级的块级元素

需要注意的是,将考虑的元素限制在这个范围是有必要的,以便一开始就保持简单。随着后面的研究,将来可能会添加其它元素。

计算方法

元素大小的计算

LCP报告的元素的大小通常是用户在视口中可见的大小。 在视口之外、被裁剪或具有不可见的溢出的元素部分不会计入元素的大小。图片元素的大小为在视口中的可见大小或者本身大小,以较小的值为准。文本元素仅考虑其文本节点的大小(包含所有文本节点的最小矩形)。对于所有元素的大小,计算式不考虑所有的margin、padding和border。

LCP报告的时间点

因为网页通常是分阶段加载的,所以页面上最大的元素可能会不断发生变化。为了处理这种潜在的变化,浏览器会在绘制第一帧后立即调度一个类型为largest-contentful-paintPerformanceEntry,用来标识最大的内容元素。后续的渲染帧只要有更大的内容元素,将会给它分配另一个PerformanceEntry

除了延迟加载的图像和字体外,页面可能会向DOM添加新元素。 如果这些新元素中的任何一个大于先前的最大内容元素,那么还会报告新的PerformanceEntry

如果页面从DOM中删除了一个元素,则不再考虑该元素。 同样,如果元素的关联图像资源发生更改(例如通过js动态地更改img.src),则该元素将被停止考虑,直到加载新图像为止。

一旦用户与页面交互(点击、滚动或按键),浏览器将停止报告新的entries,因为用户交互通常会改变用户可见的内容(尤其是滚动)。

改善方法

LCP主要受以下几个因素影响:

  • 响应时间慢的服务器。浏览器从服务器接收内容花费的时间越长,在屏幕上呈现任何内容所花费的时间就越长。 更快的服务器响应时间可以直接改善包括LCP在内的每个页面加载指标。
  • 阻塞渲染的JavaScript和CSS。浏览器呈现内容之前,需要将HTML解析为DOM树。如果遇到任何外部样式表<link rel =“ stylesheet”>或同步JavaScript标签<script src =“ main.js”>,HTML的解析将会暂停,从而延迟LCP。
  • 客户端渲染
  • 资源加载时间

所以有以下改善方法:

  • 使用CDN

  • 优化关键渲染路径

  • 服务端渲染

  • 缓存静态资源。如果你的页面是静态的,不需要在每个请求上都进行更改和变化,则缓存可以防止不必要的重新请求。 通过将生成页面存储在磁盘上,服务器端缓存可以减少TTFB并最大程度地减少资源使用。可以配置反向代理(nginx、Varnish)进行缓存内容或者在应用服务器之前使用缓存服务器。

  • 尽早地建立第三方连接。服务器对第三方来源的请求也会影响LCP,尤其是在需要在页面上显示关键内容的情况。 使用rel =“ preconnect”通知浏览器页面尽快建立连接。

    <link rel="preconnect" href="https://example.com"><!-- 使用dis-prefetch预解析DNS --><link rel="dns-prefetch" href="https://example.com">
  • 预加载重要的资源。在某个CSS或JavaScript文件中使用的重要资源可能会比想要的加载时间晚,例如动态修改src的图片。可以使用<link rel =“ preload”>更早地加载。

    <link rel="preload" as="script" href="script.js"><link rel="preload" as="style" href="style.css"><link rel="preload" as="image" href="img.png"><link rel="preload" as="video" href="vid.webm" type="video/webm"><link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin><!-- 从Chrome 83开始,可以将预加载与响应式图像一起使用,以结合两种模式以更快地加载图像。 --><link 
      rel="preload" 
      as="image" 
      href="wolf.jpg" 
      imagesrcset="wolf_400px.jpg 400w, wolf_800px.jpg 800w, wolf_1600px.jpg 1600w"
      imagesizes="50vw">
  • 自适应服务。可以根据用户的设备或网络条件获取不同的资源,有效地减少设备和网络对页面加载的影响。可以使用Network InformationDevice Memory 和 HardwareConcurrency API。

    例如,低于4G的连接速度时,可以显示图像替代视频:

    if (navigator.connection && navigator.connection.effectiveType) {
      if (navigator.connection.effectiveType === '4g') {
        // 显示视频
      } else {
        // 显示图片
      }}// navigator.connection.effectiveType: 网络类型// navigator.connection.saveData: 打开/请求数据保护模式// navigator.hardwareConcurrency: CPU核心数// navigator.deviceMemory: 设备内存

FID

在页面中,良好的第一印象会让用户留下深刻的印象。页面的第一印象也取决于很多方面,例如网页的设计和视觉吸引力,还有网页的响应速度。网页的设计很难通过一个api来进行衡量,但是响应速度可以。

FID用于衡量用户对网页的交互性和响应性的第一印象,即从用户首次与页面交互到浏览器实际上能够相应该交互之间的时间。

作为开发者,我们通常认为我们的代码将在事件触发后立即执行。但是作为用户,我们经常遇到相反的情况。如果在我们与页面尝试进行交互的时候没有得到反馈,这将对网站的第一印象大打折扣。通常这种输入延迟是由于浏览器的主线程正忙于执行其它的操作(例如解析和加载非常大的文件)阻塞而导致,无法执行触发事件的响应。

没有进行事件监听的交互,FID将会测量收到输入事件与下一次主线程空闲之间的增量。所以在未注册事件监听的情况下也可以测量FID。这是因为很多用户交互不需要事件监听,但是需要主线程空闲时才能运行。例如以下的HTML元素都需要在响应用户交互之前等待主线程上正在进行的任务完成:

  • 文本输入、单选/复选框(<input><textarea>
  • 下拉菜单(<select>
  • 超链接(<a>

改善方法

  • 减少第三方代码的影响

  • 优化JavaScript代码执行的时间

    • 减小并压缩JavaScript文件

    • 延迟未使用的JavaScript。使用Chrome开发者工具里的Coverage可以查看Unused Bytes。使用defer/async属性延迟不关键的JavaScript文件。

    • 代码拆分(Code-splitting)。将单个大型JavaScript bundle拆分为可以有条件加载的较小块chunks(也称为懒加载)。 大多数现代浏览器都支持动态import语法,该语法允许按需提取模块:

      import('module.js')	
        .then((module) => {	
          // ...
        });	
    • 减少引入不必要的polyfills。如果使用了Babel,可以使用@babel/preset-env根据浏览器兼容要求设置需要引入的polyfills。

  • 拆分长任务(Long Tasks)。任何阻塞主线程50毫秒或更长时间的代码段都可以称为长任务。拆分长任务可以减少网站上的输入延迟。使用Web Workers可以在将一些耗时的数据处理操作从主线程中剥离,使主线程更加专注于页面渲染和交互,将非UI操作放到单独的工作线程上,减少主线程的阻塞时间,从而改善FID。

  • 优化请求数量和传输量