Skip to content

Web Vitals 核心指标

以前衡量一个网站的好坏,所使用的指标很多,而且各有不同。

所以Google推出了 Web Vitals, 定义了指标集,旨在简化和统一衡量网站质量的指标。

在Web Vitals 指标中,Core Web Vitals 是其中最核心的部分,包含三个指标:

1687750882335.png

LCP

什么是 LCP?

最大内容绘制 (LCP) 指标会根据页面首次开始加载的时间点来报告可视区域内可见的最大图像或文本块完成渲染的相对时间。

良好的 FCP 值是 1.8 秒或更短,差的值大于 3.0 秒,中间的任何东西都需要改进

根据页面开始加载的时间报告可视区域内可见的最大图像或文本块完成渲染的计算时间,用于测验加载性能,衡量网站初次载入速度。 我们应该控制该值在2.5 秒以内。

最大其实就是指元素的尺寸大小,这个大小不包括可视区域之外或者是被裁剪的不可见的溢出。也不包括元素的Margin / Padding / Border等。

计算包括在内的元素有:

  • img 标签元素
  • 内嵌在<svg>元素内的<image>元素
  • video 标签元素的封面元素
  • 通过 url() 函数加载的带有背景图像的元素
  • 包含文字节点的块级元素 或 行内元素

一般网页是分批加载的,因此所谓最大元素也是随着时间变化的,浏览器在在绘制第一帧时分发一个largest-contentful-paint类型PerformanceEntry对象,随着时间的渲染,当有更大的元素渲染完成时,就会有另一个PerformanceEntry对象报告。

利用 PerformanceObserver 构造函数创建一个性能检测对象,可以通过以下代码打印采集数据

javascript
let observer = new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    console.log('LCP candidate:', entry.startTime, entry);
  }
});
observer.observe({ type: "largest-contentful-paint", buffered: true });

一般来说,通过上面的代码,最新的largest-contentful-paint条目的startTime就是LCP值。

web-vitals库

示例

以下示例展示了一些热门网站上出现最大内容绘制的时间点:

来自 cnn.com 的最大内容绘制时间轴

来自 techcrunch.com 的最大内容绘制时间轴

在上方的两个时间轴中,最大元素随内容加载而变化。在第一个示例中,新内容被添加进 DOM,并因此使最大元素发生了改变。在第二个示例中,由于布局的改变,先前的最大内容从可视区域中被移除。

虽然延迟加载的内容通常比页面上已有的内容更大,但实际情况并非一定如此。接下来的两个示例显示了在页面完全加载之前出现的最大内容绘制。

来自 instagram.com 的最大内容绘制时间轴

来自 google.com 的最大内容绘制时间轴

在第一个示例中,Instagram 标志加载得相对较早,即使其他内容随后陆续显示,但标志始终是最大元素。在 Google 搜索结果页面示例中,最大元素是一段文本,这段文本在所有图像或标志完成加载之前就显示了出来。由于所有单个图像都小于这段文字,因此这段文字在整个加载过程中始终是最大元素。

在 Instagram 时间轴的第一帧中,您可能注意到了相机标志的周围没有用绿框框出。这是因为该标志是一个<svg>元素,而<svg>元素目前不被视为 LCP 候选对象。首个 LCP 候选对象是第二帧中的文本。

如何改进 LCP

要了解如何改进某个特定网站的 FCP,您可以运行一次灯塔性能审计,并留心查看审计建议的各种具体机会诊断

要了解改进 FCP 的常见方式(针对任何网站),请参阅以下性能指南:

FID

什么是 FID?

FID 测量从用户第一次与页面交互(例如当他们单击链接、点按按钮或使用由 JavaScript 驱动的自定义控件)直到浏览器对交互作出响应,并实际能够开始处理事件处理程序所经过的时间。

好的 fid 值为 2.5 秒,差的值大于 4.0 秒,中间的任何值都需要改进

FID 详情

作为编写事件响应代码的开发者,我们通常会假定代码会在事件发生时立即运行。但作为用户,我们都常常面临相反的情况,当我们在手机上加载了一个网页并试图与网页交互,接着却因为网页没有任何反应而感到沮丧。

一般来说,发生输入延迟(又称输入延时)是因为浏览器的主线程正忙着执行其他工作,所以(还)不能响应用户。可能导致这种情况发生的一种常见原因是浏览器正忙于解析和执行由您的应用程序加载的大型 JavaScript 文件。在此过程中,浏览器不能运行任何事件侦听器,因为正在加载的 JavaScript 可能会让浏览器去执行其他工作。

请看以下这条典型的网页加载时间轴:

示例页面加载跟踪

上方的可视化图表中显示的是一个页面,该页面正在发出数个网络请求来获取资源(多为 CSS 和 JS 文件),这些资源下载完毕后,会在主线程上进行处理。

这就导致主线程会阶段性地处于忙碌状态(在图中表示为米黄色任务块)。

较长的首次输入延迟通常发生在首次内容绘制 (FCP)Time to Interactive 可交互时间 (TTI)之间,因为在此期间,页面已经渲染出部分内容,但交互性还尚不可靠。为了说明这种情况的发生缘由,我们在时间轴中加入了 FCP 和 TTI:

带有 FCP 和 TTI 的示例页面加载跟踪

您可能已经注意到 FCP 和 TTI 之间有相当长的一段时间(包括三段长任务),如果用户在这段时间内尝试与页面进行交互(例如单击一个链接),那么从浏览器接收到单击直至主线程能够响应之前就会有一段延迟。

请看如果用户在最长的任务刚开始时就尝试与页面进行交互会发生什么:

带有 FCP、TTI 和 FID 的示例页面加载跟踪

因为输入发生在浏览器正在运行任务的过程中,所以浏览器必须等到任务完成后才能对输入作出响应。浏览器必须等待的这段时间就是这位用户在该页面上体验到的 FID 值。

如果交互没有事件侦听器怎么办?

FID 测量接收到输入事件的时间点与主线程下一次空闲的时间点之间的差值。这就意味着**即使在尚未注册事件侦听器的情况下,**FID 也会得到测量。这是因为许多用户交互的执行并不需要事件侦听器,但一定需要主线程处于空闲期。

例如,在对用户交互进行响应前,以下所有 HTML 元素都需要等待主线程上正在进行的任务完成运行:

  • 文本字段、复选框和单选按钮 (<input> <textarea>)
  • 下拉选择列表(<select>
  • 链接 (<a>)

为什么只考虑首次输入?

虽然任何输入延迟都可能导致糟糕的用户体验,但我们主要建议您测量首次输入延迟,原因如下:

首次输入延迟将会是用户对您网站响应度的第一印象,而第一印象对于塑造我们对网站质量和可靠性的整体印象至关重要。

我们现如今在网络上看到的最大的交互性问题发生在页面加载期间。因此,我们认为首先侧重于改善网站的首次用户交互将对改善网络的整体交互性产生最大的影响。

我们推荐网站针对较高的首次输入延迟采取的解决方案(代码拆分、减少 JavaScript 的预先加载量等)不一定与针对页面加载后输入延迟缓慢的解决方案相同。通过分离这些指标,我们将能够为网页开发者提供更确切的性能指南。

哪些算是首次输入?

FID 是测量页面加载期间响应度的指标。因此,FID 只关注不连续操作对应的输入事件,如点击、轻触和按键。

其他诸如滚动和缩放之类的交互属于连续操作,具有完全不同的性能约束(而且,浏览器通常能够通过在单独的线程上执行这些操作来隐藏延迟)。

换句话说,FID 侧重于RAIL 性能模型中的 R(响应度),而滚动和缩放与 A(动画)更为相关,因此这些操作的性能质量应该单独进行评估。

如果用户始终没有与您的网站进行交互怎么办?

并非所有用户都会在每次访问您的网站时进行交互。而且也并不是所有交互都与 FID 相关(如上一节所述)。此外,一些用户的首次交互会处于不利的时间段内(当主线程长时间处于繁忙时),而另一些用户的首次交互会处于有利的时间段内(当主线程完全空闲时)。

这意味着有些用户将没有 FID 值,有些用户的 FID 值较低,而有些用户的 FID 值可能较高。

您对 FID 的跟踪、报告和分析方式可能与您惯常使用的其他指标十分不同。下一节将说明相应的最佳做法。

为什么只考虑输入延迟?

如上所述,FID 只测量事件处理过程中的"延迟"。FID 既不测量事件处理本身所花费的时间,也不测量浏览器在运行事件处理程序后更新用户界面所花费的时间。

虽然这些时间对用户来说非常重要,也确实会影响用户体验,但这些时间并不包括在该项指标内,因为这样的做法可能会激励开发者加入变通方案,并因此导致体验变得更加糟糕,这里的意思是说,开发者可以将事件处理程序逻辑封装在一个异步回调中(通过setTimeout()或requestAnimationFrame()),从而将逻辑与事件关联的任务分离。最终的结果虽然能够提升指标分数,但会使用户感知到的响应速度变慢。

不过,虽然 FID 只测量事件延时的"延迟"部分,但想要对事件生命周期进行更多跟踪的开发者可以使用事件计时 API来实现这一想法。如需更多详情,请参阅自定义指标的相关指导。

如何测量 FID

FID 是一个只能进行实际测量的指标,因为该项指标需要真实用户与您的页面进行交互。您可以使用以下工具测量 FID。

FID 需要真实用户,因此无法在实验室中进行测量。但是,Total Blocking Time 总阻塞时间 (TBT)指标不仅可以进行实验室测量,还与实际的 FID 关联性强,而且可以捕获影响交互性的问题。能够在实验室中改进 TBT 的优化也应该能为您的用户改进 FID。

在 JavaScript 中测量 FID

要在 JavaScript 中测量 FID,您可以使用事件计时 API。以下示例说明了如何创建一个PerformanceObserver来侦听first-input条目并记录在控制台中:

javascript
new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    const delay = entry.processingStart - entry.startTime;
    console.log('FID candidate:', delay, entry);
  }
}).observe({type: 'first-input', buffered: true});

警告

上述代码说明了如何将first-input条目记录在控制台中并计算延迟。但是,在 JavaScript 中测量 FID 要更为复杂。详情请见下文:

在上方的示例中,first-input条目的延迟值是通过获取条目的startTimeprocessingStart时间戳之间的差值来测量的。在大多数情况下,这个差值就是 FID 值,然而,并非所有first-input条目都能够用来测量 FID

以下部分列出了 API 报告的内容与指标计算方式之间的差异。

指标和 API 之间的差异

API 会为在后台选项卡中加载的页面分发first-input条目,但在计算 FID 时应忽略这些页面。

如果页面在首次输入发生前转移到后台,API 也会分发first-input条目,但在计算 FID 时仍应忽略这些页面(只有当页面始终处于前台时才考虑输入)。

当页面通过往返缓存恢复时,API 不会报告first-input条目,但在这些情况下应该测量 FID,因为这对用户来说是多次不同的页面访问体验。

API 不会报告 iframe 中的输入,但要想正确测量 FID,您应该考虑这些输入。子框架可以使用 API 将这些输入的first-input条目报告给父框架来进行聚合。

开发者不必记住所有这些细微差异,而是可以使用web-vitalsJavaScript 库来测量 FID,库会自行处理这些差异(在可能的情况下):

javascript
import {onFID} from 'web-vitals';

// 当 FID 可用时立即进行测量和记录。
onFID(console.log);

如何改进 FID

要了解如何改进某个特定网站的 FID,您可以运行一次灯塔性能审计,并留心查看审计建议的各种具体机会

虽然 FID 是一项实际指标(而灯塔是一个实验室指标工具),但改进 FID 的指导方向与改进总阻塞时间 (TBT)这项实验室指标的指导方向相同。

如需深入了解如何改进 FID,请参阅优化 FID。有关其他能够改进 FID 的单个性能技巧的进一步指导,请参阅:

Cumulative Layout Shift 累积布局偏移 (CLS)

您是否曾经历过在网上阅读一篇文章,结果页面上的某些内容突然发生改变?文本在毫无预警的情况下移位,导致您找不到先前阅读的位置。或者更糟糕的情况:您正要点击一个链接或一个按钮,但在您手指落下的瞬间,诶?链接移位了,结果您点到了别的东西!

大多数情况下,这些体验只是令人恼火,但在某些情况下,却可能带来真正的破坏。

1687767504972.gif

页面内容的意外移动通常是由于异步加载资源,或者动态添加 DOM 元素到页面现有内容的上方。罪魁祸首可能是未知尺寸的图像或视频、实际渲染后比后备字体更大或更小的字体,或者是动态调整自身大小的第三方广告或小组件。

让这个问题变得更加棘手的是,网站在开发环境中的运作方式通常与用户在网站上的实际体验大不相同。个性化或第三方内容在开发环境中的表现通常与其在实际情况中的表现不同,测试图像通常已经在开发者的浏览器缓存中了,并且本地调用 API 的速度一般非常快,几乎察觉不到延迟。

累积布局偏移 (CLS) 指标通过测量真实用户体验中发生偏移的频率来帮助您解决这一问题。

什么是 CLS?

CLS 测量整个页面生命周期内发生的所有意外布局偏移中最大一连串的布局偏移分数

每当一个可见元素的位置从一个已渲染帧变更到下一个已渲染帧时,就发生了布局偏移 。(有关单次布局偏移分数计算方式的详细信息,请参阅下文。)

一连串的布局偏移,也叫会话窗口,是指一个或多个快速连续发生的单次布局偏移,每次偏移相隔的时间少于 1 秒,且整个窗口的最大持续时长为 5 秒。

最大的一连串是指窗口内所有布局偏移累计分数最大的会话窗口。

1687768197201.gif

怎样算是良好的 CLS 分数?

为了提供良好的用户体验,网站应该努力将 CLS 分数控制在0.1 或以下。为了确保您能够在大部分用户的访问期间达成建议目标值,一个良好的测量阈值为页面加载的第 75 个百分位数,且该阈值同时适用于移动和桌面设备。

良好的 CLS 值低于 0.1,较差的值大于 0.25 并且介于两者之间的任何东西都需要改进

布局偏移详情

布局偏移由布局不稳定性 API定义,只要可视区域中可见元素的起始位置(例如,元素在默认书写模式下的顶部和左侧位置)在两帧之间发生了变更,该 API 就会报告layout-shift条目。这样的元素被视为不稳定元素

请注意,只有当现有元素的起始位置发生变更时才算作布局偏移。如果将新元素添加到 DOM 或是现有元素更改大小,则不算作布局偏移,前提是元素的变更不会导致其他可见元素的起始位置发生改变。

布局偏移分数

浏览器在计算布局偏移分数时,会查看可视区域大小和两个已渲染帧之间的可视区域中不稳定元素的位移。布局偏移分数是该位移的两个度量的乘积:影响分数距离分数(两者定义如下)。

txt
布局偏移分数 = 影响分数 * 距离分数

影响分数

影响分数测量不稳定元素对两帧之间的可视区域产生的影响。

前一帧当前帧的所有不稳定元素的可见区域集合(占总可视区域的部分)就是当前帧的影响分数

包含一个不稳定元素的影响分数示例

在上图中,有一个元素在一帧中占据了一半的可视区域。接着,在下一帧中,元素下移了可视区域高度的 25%。红色虚线矩形框表示两帧中元素的可见区域集合,在本示例中,该集合占总可视区域的 75%,因此其影响分数0.75

距离分数

布局偏移分数计算公式的另一部分测量不稳定元素相对于可视区域位移的距离。距离分数指的是任何不稳定元素在一帧中位移的最大距离(水平或垂直)除以可视区域的最大尺寸维度(宽度或高度,以较大者为准)。

包含一个不稳定元素的距离分数示例

在上方的示例中,最大的可视区域尺寸维度是高度,不稳定元素的位移距离为可视区域高度的 25%,因此距离分数为 0.25。

所以,在这个示例中,影响分数0.75距离分数0.25 ,所以布局偏移分数0.75 * 0.25 = 0.1875

最初,布局偏移分数只根据影响分数进行计算。引入距离分数是为了避免在大元素发生微小位移的情况下进行过度惩罚的情况。

下一个示例说明了向现有元素添加内容对布局偏移分数的影响:

包含稳定和不稳定元素及可视区域剪裁的布局偏移示例

"点我!"按钮附加在了包含黑色字体文本的灰色框底部,将带有白色字体文本的绿色框下推(且一部分已在可视区域外)。

在这个示例中,灰色框的大小改变了,但起始位置没有改变,所以该灰色框不是一个不稳定元素

"点我!"按钮一开始并不在 DOM 中,所以它的起始位置也没有改变。

然而,绿色框的起始位置确实发生了变化,但由于绿色框的一部分已经在可视区域外,因此在计算影响分数时不考虑不可见区域。两帧中绿色框的可见区域集合(由红色虚线矩形框表示)与第一帧中绿色框的区域相同,为可视区域的 50%。影响分数0.5

距离分数由紫色箭头表示。绿色框向下移动的距离大约为可视区域的 14%,因此距离分数0.14

布局偏移分数是0.5 x 0.14 = 0.07

最后一个示例说明了多个不稳定元素的情况:

包含多个稳定和不稳定元素的布局偏移示例

在上面的第一帧中,有四个 API 请求的动物名称结果,按首字母顺序进行了排序。在第二帧中,更多的结果被添加到排序列表中。

列表中第一项("Cat")的起始位置没有在两帧之间发生改变,因此是稳定的。同样,添加到列表中的新项目之前不在 DOM 中,因此这些项目的起始位置也没有改变。但"Dog"、"Horse"和"Zebra"这几项的起始位置都发生了改变,因此都是不稳定元素

同理,红色虚线矩形框代表这三个不稳定元素前后两帧的区域集合,在本示例中,大约占可视区域面积的 38%(影响分数0.38 )。

箭头表示不稳定元素相对于起始位置的位移距离。由蓝色箭头表示的"Zebra"元素位移的距离最大,约为可视区域高度的 30%。因此本示例中的距离分数0.3

布局偏移分数是0.38 x 0.3 = 0.1172

预期布局偏移 vs. 意外布局偏移

布局偏移并不总是坏事。事实上,许多动态网络应用程序经常更改页面元素的起始位置。

由用户发起的布局偏移

布局偏移只有在用户并不期望其发生时才算是坏事。换言之,对用户交互(单击链接、点选按钮、在搜索框中键入信息等)进行响应的布局偏移通常没有问题,前提是偏移发生的时机与交互时机足够接近,使用户对前后关系一目了然。

例如,如果某次用户交互触发了一个网络请求,而该请求可能需要一段时间才能完成,那么最好立即留出一些空间并显示加载指示器,避免在请求完成时出现令用户不快的布局偏移。如果用户没有意识到当前正在加载某些内容,或者不知道资源什么时候能够准备就绪,他们就可能会在等待期间尝试点击其他内容(来打破僵局)。

在用户输入 500 毫秒内发生的布局偏移会带有hadRecentInput标志,便于在计算中排除这些偏移。

动画和过渡

动画和过渡如果做得好,确实是一个在更新页面内容时不让用户感到突兀的好方法。页面内容突然发生意外偏移几乎无一例外会造成糟糕的用户体验。但如果内容从一个位置逐步又自然地移动到下一个位置,这通常有助于用户更好地明白当前状况,并引导他们适应状态之间的变化。

CSS transform属性使您能够在不触发布局偏移的情况下为元素设置动画:

  • transform: scale()来替代和调整heightwidth属性。
  • 如需使元素能够四处移动,可以用transform: translate()来替代和调整toprightbottomleft属性。

在 JavaScript 中测量 CLS

要在 JavaScript 中测量 CLS,您可以使用布局不稳定性 API。以下示例说明了如何创建一个PerformanceObserver来侦听意外layout-shift条目、将条目按会话分组、记录最大会话值,并在最大会话值发生改变时更新记录。

js
let clsValue = 0;
let clsEntries = [];

let sessionValue = 0;
let sessionEntries = [];

new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    // 只将不带有最近用户输入标志的布局偏移计算在内。
    if (!entry.hadRecentInput) {
      const firstSessionEntry = sessionEntries[0];
      const lastSessionEntry = sessionEntries[sessionEntries.length - 1];

      // 如果条目与上一条目的相隔时间小于 1 秒且
      // 与会话中第一个条目的相隔时间小于 5 秒,那么将条目
      // 包含在当前会话中。否则,开始一个新会话。
      if (sessionValue &&
          entry.startTime - lastSessionEntry.startTime < 1000 &&
          entry.startTime - firstSessionEntry.startTime < 5000) {
        sessionValue += entry.value;
        sessionEntries.push(entry);
      } else {
        sessionValue = entry.value;
        sessionEntries = [entry];
      }

      // 如果当前会话值大于当前 CLS 值,
      // 那么更新 CLS 及其相关条目。
      if (sessionValue > clsValue) {
        clsValue = sessionValue;
        clsEntries = sessionEntries;

        // 将更新值(及其条目)记录在控制台中。
        console.log('CLS:', clsValue, clsEntries)
      }
    }
  }
}).observe({type: 'layout-shift', buffered: true});

可以使用web-vitals JavaScript 库来测量 CLS,库会自行处理上述所有情况:

js
import {onCLS} from 'web-vitals';

// 在所有需要汇报 CLS 的情况下
// 对其进行测量和记录。
onCLS(console.log);

如何改进 CLS

对于大多数网站来说,您可以通过遵循一些指导原则来避免所有的意外布局偏移:

  • **始终在您的图像和视频元素上包含尺寸属性,或者通过使用CSS 长宽比容器之类的方式预留所需的空间。**这种方法可以确保浏览器能够在加载图像期间在文档中分配正确的空间大小。请注意,您还可以使用unsized-media 功能策略在支持功能策略的浏览器中强制执行此行为。
  • **除非是对用户交互做出响应,否则切勿在现有内容的上方插入内容。**这样能够确保发生的任何布局偏移都在预期之内。
  • **首选转换动画,而不是触发布局偏移的属性动画。**动画过渡的目标是提供状态与状态之间的上下文连续性。

如需深入了解如何改进 CLS,请参阅优化 CLS调试布局偏移

前端知识体系 · wcrane