朝着更出色的响应能力指标迈进

了解我们关于衡量响应速度的想法,并向我们提供反馈。

Annie Sullivan
Annie Sullivan
宋红波
Hongbo Song
尼古拉斯·佩尼亚·莫雷诺
Nicolás Peña Moreno

Chrome 速度指标团队致力于加深对网页响应用户输入的速度的理解。我们想分享一些有关改进响应速度指标的想法,并听取您的反馈。

本博文将涵盖两个主要主题:

  1. 查看我们当前的响应能力指标 First Input Delay (FID),并说明我们为什么选择 FID 而不是一些替代方案。
  2. 下面介绍一些我们一直在考虑的改进,它们应该能够更好地捕获各个事件的端到端延迟时间。这些改进还旨在更全面地了解网页在整个生命周期内的整体响应情况。

什么是 First Input Delay?

首次输入延迟 (FID) 指标用于衡量浏览器开始处理网页上的首次用户互动所需的时间。具体而言,它测量的是用户与设备互动的时间与浏览器实际能够开始处理事件处理脚本的时间之间的差异。仅衡量点按和按键的 FID,这意味着它仅考虑以下事件的首次发生:

  • click
  • keydown
  • mousedown
  • pointerdown(仅当后跟 pointerup 时)

下图展示了 FID:

First Input Delay 用于测量从输入发生到可以处理输入的时间范围。

FID 不包含运行这些事件处理脚本所花费的时间,也不包含浏览器此后为更新屏幕而完成的任何工作。它测量的是主线程在有机会处理输入之前处于忙碌状态的时间。这种阻塞时间通常是由较长的 JavaScript 任务导致的,因为这些任务无法随时停止,所以浏览器必须先完成当前任务,然后才能开始处理输入。

我们为什么选择 FID?

我们认为,衡量实际用户体验非常重要,这样才能确保指标的改进能够给用户带来真正的好处。我们选择衡量 FID 是因为它代表了当用户决定与刚刚加载的网站互动时,用户体验涉及的一部分。FID 会捕获用户必须等待一段时间才能看到与网站互动的响应。换句话说,FID 是用户在互动后等待时间的下限。

总阻塞时间 (TBT)可交互时间 (TTI) 等其他指标均基于长任务,并且与 FID 一样,也会测量负载期间的主线程阻塞时间。由于这些指标在现场和实验室中都可以测量,因此许多开发者都询问为什么我们不首选使用其中一项指标,而不是 FID。

导致这种情况的原因有以下几种。也许最重要的原因是这些指标无法直接衡量用户体验。所有这些指标都会衡量 JavaScript 在网页上运行的量。虽然长时间运行的 JavaScript 确实会导致网站出现问题,但如果用户在发生时未与网页互动,这些任务并不一定会影响用户体验。网页的 TBT 和 TTI 得分可能较高,但用户感觉很慢,或者得分很低,但用户感觉很快。根据我们的经验,这些间接衡量的结果对某些网站来说效果很好,但对大多数网站来说却不好。简而言之,长任务和 TTI 并非以用户为中心这一事实,导致这些任务较弱。

虽然实验室测量无疑是一个非常重要的诊断工具,但真正重要的是用户的网站体验。通过采用以用户为中心的指标来反映实际用户情况,您可以保证获得对体验有意义的信息。我们决定从该体验的一小部分开始,尽管我们知道这部分体验并不能代表完整体验。因此,我们正在努力捕获更多用户等待其输入得到处理的时间。

有关测量现场 TTI 的注意事项

针对实际用户的 TTI 衡量存在问题,因为它在网页加载很晚发生。在计算 TTI 之前,必须要有 5 秒的网络静默窗口。在该实验中,您可以选择在获得所有需要的数据后卸载页面,但现场的实时用户监控不是这种情况。用户可以随时选择离开网页或与网页互动。特别是,用户可能会选择离开需要很长时间才能加载的网页,在这种情况下,系统不会记录准确的 TTI。在针对 Chrome 中的真实用户衡量 TTI 时,我们发现只有大约一半的网页加载达到了 TTI。

我们正在考虑哪些改进?

我们希望开发一个新指标来扩展 FID 当前衡量的指标,同时仍保持其与用户体验的紧密联系。

我们希望这个新指标能够:

  1. 考虑所有用户输入(而不只是第一项)的响应速度
  2. 捕获每个事件的完整时长(而不仅仅是延迟时间)。
  3. 将在同一逻辑用户互动中发生的事件组合在一起,并将该互动的延迟时间定义为其所有事件的最长持续时间。
  4. 为页面上发生的所有互动在整个生命周期内创建汇总分数。

要取得成功,我们应该有信心地宣布,如果某个网站在这个新指标上得分不佳,它就无法快速响应用户互动。

捕获完整的活动时长

第一个显而易见的改进是尝试捕获事件的端到端延迟时间。如上所述,FID 仅捕获输入事件的延迟部分。它并未考虑浏览器实际处理事件处理脚本所需的时间。

事件生命周期分为不同的阶段,如下图所示:

事件生命周期的五个步骤

Chrome 处理输入的步骤如下:

  1. 用户输入。发生这种情况的时间是事件的 timeStamp
  2. 浏览器会执行命中测试,以确定事件属于哪个 HTML 框架(主框架或某个 iframe)。然后,浏览器将该事件发送到负责该 HTML 帧的相应渲染程序进程。
  3. 渲染程序收到该事件并将其加入队列,以便在有可用事件时对其进行处理。
  4. 渲染程序通过运行其处理程序来处理事件。这些处理程序可以将输入处理过程中的其他异步工作(例如 setTimeout 和提取)排入队列。但此时同步工作已经完成。
  5. 屏幕上会绘制一帧,以反映事件处理脚本运行的结果。请注意,由事件处理脚本排入队列的所有异步任务可能仍未完成。

上述第 (1) 步和第 (3) 步之间的间隔时间属于事件的延迟,即 FID 所衡量的内容。

上述第 (1) 步和第 (5) 步之间的间隔时间即为事件的时长。我们的新指标将会衡量这一点。

事件的时长包含延迟时间,但还包括事件处理脚本中发生的工作,以及浏览器在这些处理程序运行后绘制下一帧需要完成的工作。目前,事件时长可通过条目的 duration 属性在 Event Timing API 中获得。

异步任务注意事项

理想情况下,我们希望同时捕获由事件触发的异步工作。但问题在于,由事件触发的异步工作的定义非常难以定义。例如,开发者可以选择在事件处理脚本上开始播放某些动画,并使用 setTimeout 开始播放此类动画。如果我们捕获了在处理程序上发布的所有任务,那么只要动画运行,动画就会延迟完成时间。我们认为,有必要研究如何使用启发法来捕获异步且应尽快完成的工作。不过,我们不希望在这样做时格外小心,因为我们不希望惩罚那些本应花费很长时间才能完成的工作。因此,我们的初始工作会将第 5 步视为终点:它只会考虑同步工作以及此类工作完成后绘制所用的时间。也就是说,在初始工作中,我们不会应用启发法来猜测第 4 步中异步启动的工作。

值得注意的是,在许多情况下,工作应该同步执行。实际上,这种情况可能无法避免,因为系统有时会连续分派事件,并且事件处理脚本需要按顺序执行。也就是说,我们仍会错过重要工作,例如会触发提取的事件或依赖于在下一个 requestAnimationFrame 回调中完成的重要工作的事件。

将事件划分为互动

最好将指标衡量从“延迟”扩展为“持续时间”,但这在指标上仍然存在重大缺口:它侧重于单个事件,而不是与网页互动的用户体验。

单次用户互动可能会触发许多不同的事件,单独衡量每个事件并不能清楚地了解用户体验。我们希望确保指标能够尽可能准确地捕获用户在点按、按下按键、滚动和拖动操作时等待响应的完整时间。因此,我们引入了互动的概念来衡量每次互动的延迟时间。

互动类型

下表列出了我们想要定义的四种互动及其关联的 DOM 事件。请注意,这与在发生此类用户互动时分派的所有事件并不完全相同。例如,当用户滚动时,系统会分派滚动事件,但该事件发生在屏幕更新以反映滚动之后,因此我们不会将其视为互动延迟的一部分。

互动 开始 / 结束 桌面设备事件 移动事件
键盘 已按下按键 keydown keydown
keypress keypress
已释放密钥 keyup keyup
点按或拖动 点按“开始”或拖动开始 pointerdown pointerdown
mousedown touchstart
点按向上或拖动末尾 pointerup pointerup
mouseup touchend
click mousedown
mouseup
click
滚动 不适用
每种互动类型的 DOM 事件。

上面列出的前三次互动(键盘、点按和拖动)目前在 FID 的涵盖范围内。对于我们新的响应能力指标,我们还希望包括滚动操作,因为滚动操作在网络上非常常见,并且是网页给用户带来的感觉的一个重要方面。

开头和结尾的注意事项

请注意,每次互动均包括两个部分:当用户按下鼠标、手指或按键时,以及当用户抬起时。我们需要确保我们的指标不会将用户在这两个操作之间按住手指所花的时间计入网页延迟时间!

键盘

键盘互动包含两个部分:当用户按下键时和当用户松开键时。 有三个与此用户互动相关联的事件:keydownkeyupkeypress。下图说明了键盘交互的 keydownkeyup 延迟和时长:

键盘互动与不相交的事件持续时间

在上图中,时长不相交,因为来自 keydown 更新的帧是在 keyup 发生之前呈现的,但并不总是如此。另请注意,由于生成帧所需的最后步骤在渲染器进程之外完成,因此帧可以在渲染器进程中的任务过程中呈现。

keydownkeypress 在用户按下按键时发生,而 keyup 在用户松开按键时发生。通常,主要内容更新会在用户按下相应键时发生:文本显示在屏幕上或应用修饰符效果。不过,我们希望捕获 keyup 还会带来有趣的界面更新的罕见情况,因此我们想了解所用时间。

为了捕获键盘互动所用的总时间,我们可以计算 keydownkeyup 事件的最长持续时间。

关于重复按键的注意事项

在这里需要说明一种极端情况:可能存在用户按下某个键并花一段时间才能释放的情况。在这种情况下,分派的事件序列可能会不同。在这种情况下,我们认为每个 keydown 都发生了一次互动,但该互动不一定有相应的 keyup

点按

另一个重要的用户互动是用户点按或点击网站。与 keypress 类似,有些事件会在用户按下时触发,另一些事件则会在用户松开时触发,如上图所示。请注意,与点按关联的事件在桌面设备上和移动设备上略有不同。

对于点按或点击,松开操作通常会触发大多数回应,但是与键盘交互一样,我们希望捕获完整的交互。在这种情况下,更重要的是这样做,因为在点按时进行一些界面更新实际上并不罕见。

我们希望纳入所有这些事件的事件时长,但由于其中许多事件完全重叠,我们只需衡量 pointerdownpointerupclick 即可涵盖完整的互动。

我们能否将范围进一步缩小为仅包含 pointerdownpointerup

一个初步的想法是使用 pointerdownpointerup 事件,并假设它们涵盖了您感兴趣的所有时长。遗憾的是,事实并非如此,正如此极端案例所示。请尝试在移动设备上或通过移动设备模拟打开此网站,然后点按“点击我”的区域。此网站会触发浏览器点按延迟。可以看出,pointerdownpointeruptouchend 会快速分派,而 mousedownmouseupclick 会等待延迟才会被分派。这意味着,如果我们只查看 pointerdownpointerup,就会错过合成事件的时长,而这个时长因浏览器点按延迟而较大,因此应该包含在内。因此,我们应测量 pointerdownpointerupclick 以涵盖完整的交互。

拖动

我们决定也添加拖动操作,因为此类操作具有类似的关联事件,并且通常会对网站进行重要的界面更新。但对于我们的指标,我们打算仅考虑拖动的开始和结束部分,即拖动的初始部分和最后部分。这样便于进行推理,并使延迟时间与考虑的其他互动相当。这与我们排除连续事件(例如 mouseover)的决定一致。

我们也不考虑通过 Drag and Drop API 实现的拖动,因为它们仅适用于桌面设备。

滚动

与网站互动的最常见形式之一就是滚动浏览。对于我们的新指标,我们希望测量用户初始滚动互动的延迟时间。特别是,我们注重浏览器对用户请求滚动这一事实的初始反应。本示例不会涵盖完整的滚动体验。也就是说,滚动会产生许多帧,而我们将注意力集中在响应滚动而生成的初始帧上。

为什么只有第一个呢?例如,后续帧可能会被单独的平滑度建议捕获。也就是说,向用户显示滚动的第一个结果后,其余结果应根据滚动体验的流畅程度进行衡量。因此,我们认为流畅性工作可以更好地捕获这一点。因此,与 FID 一样,我们选择坚持离散的用户体验:用户体验具有明确的关联时间点,我们可以轻松计算其延迟时间。作为一个整体,滚动是一种连续的体验,因此我们不打算在此指标中对所有滚动操作进行衡量。

那么,为什么要测量滚动次数呢?我们在 Chrome 中收集的滚动性能表明,滚动速度通常非常快。也就是说,出于各种原因,我们仍然希望在新指标中包含初始滚动延迟时间。第一,滚动之所以快速,只是因为它经过了很多优化,因为它非常重要。但是,网站仍然有方法可以避开浏览器带来的一些性能提升。Chrome 中最常见的方法是强制在主线程上进行滚动。因此,我们的指标应该能够说明发生这种情况的时间以及导致用户滚动性能不佳的时间。其次,滚动操作太重要,不该忽略。我们担心,如果我们排除滚动,将会遇到一个很大的盲点,而且滚动性能可能会随着时间的推移而下降,而 Web 开发者不会注意到这一点。

系统会在用户滚动时分派多个事件,例如 touchstarttouchmovescroll。除滚动事件外,这在很大程度上取决于用于滚动的设备:触摸事件会在移动设备上用手指滚动时分派,而滚轮事件则是在使用鼠标滚轮滚动时发生。滚动事件会在初始滚动完成后触发。一般来说,除非网站使用非被动事件监听器,否则 DOM 事件不会阻止滚动。因此,我们将滚动视为完全与 DOM 事件分离。我们要测量的是从用户移动至产生滚动手势的时间,到显示发生滚动的第一帧所用的时间。

如何定义互动的延迟时间?

如上所述,需要分别考虑包含“向下”和“向上”部分的互动,以避免将用户的手指按下的时间归因于相应互动。

对于这些类型的互动,我们希望延迟时间涵盖与其关联的所有事件的持续时间。由于互动的每个“按下”和“释放”部分的事件时长可能会重叠,因此,实现此互动延迟的最简单定义是,与其相关联的任意事件的最长时长。回顾之前的键盘示意图,该时长为 keydown 时长,因为它比 keyup 长:

突出显示时长上限的键盘互动方式

keydownkeyup 时长也可能会重叠。例如,当为两个事件呈现的帧相同时,可能会发生这种情况,如下图所示:

按下和释放在同一帧中的键盘互动

这种方法使用最大值的方法各有利弊,我们非常期待听取您的反馈

  • 优点:这与我们打算衡量滚动的方式一致,因为它仅衡量单个时长值。
  • 优点:在键盘交互等情况下,keyup 通常不会执行任何操作,而用户可能会快速或缓慢地执行按键并松开,从而降低此类情况下的噪声。
  • 缺点:它不捕捉用户的完整等待时间。例如,它会捕捉拖动开始或结束位置,但不会捕捉到两者。

对于滚动(只有一个关联事件),我们希望将其延迟时间定义为浏览器因滚动而生成第一帧所需的时间。也就是说,延迟时间是首个 DOM 事件(如 touchmove,如果使用的是手指)的 timeStamp 事件(大到触发滚动)与首次绘制(反映所发生滚动情况)之间的增量。

汇总每个页面的所有互动

定义了互动的延迟时间后,我们需要计算网页加载的聚合值,其中可能会发生多次用户互动。获得汇总值后,我们能够:

  • 表单与业务指标之间的关联。
  • 评估与其他效果指标的相关性。理想情况下,我们的新指标将足够独立,能够为现有指标增加价值。
  • 以易于理解的方式在工具中轻松公开值。

为了执行此汇总,我们需要解决两个问题:

  1. 我们会尝试汇总哪些数字?
  2. 如何汇总这些数据?

我们正在探索和评估几个选项。我们欢迎您就此汇总数据发表看法。

一种方式是为互动的延迟时间定义预算,该预算可能取决于类型(滚动、键盘、点按或拖动)。例如,如果点按的预算为 100 毫秒,点按的延迟时间为 150 毫秒,那么这次互动的超出预算时长就是 50 毫秒。然后,我们就可以计算出页面中任何用户互动超出预算的最大延迟时间。

另一种方法是计算网页整个生命周期内互动的平均延迟时间或中位数。因此,如果我们有 80 毫秒、90 毫秒和 100 毫秒的延迟时间,则网页的平均延迟时间将为 90 毫秒。我们还可以考虑“超过预算”的平均或中位数,以根据互动类型考虑不同的预期。

这在 Web 性能 API 上是什么样的?

Event Timing 中缺少了什么?

遗憾的是,本博文中呈现的所有想法均无法使用 Event Timing API 捕获。具体而言,要知道与 API 的指定用户互动相关的事件,没有什么简单的方法可以找到的。为此,我们提议向 API 添加 interactionID

Event Timing API 的另一个缺点是无法衡量滚动互动,因此我们致力于实现这些衡量(通过 Event Timing 或单独的 API)。

你现在可以试试什么?

目前,您仍然可以计算点按/拖动和键盘互动的最长延迟时间。以下代码段会生成这两个指标。

let maxTapOrDragDuration = 0;
let maxKeyboardDuration = 0;
const observer = new PerformanceObserver(list => {
  list.getEntries().forEach(entry => {
    switch(entry.name) {
      case "keydown":
      case "keyup":
        maxKeyboardDuration = Math.max(maxKeyboardDuration,
            entry.duration);
        break;
      case "pointerdown":
      case "pointerup":
      case "click":
        maxTapOrDragDuration = Math.max(maxTapOrDragDuration,
            entry.duration);
        break;
    }
  });
});
observer.observe({type: "event", durationThreshold: 16, buffered: true});
// We can report maxTapDragDuration and maxKeyboardDuration when sending
// metrics to analytics.

反馈

如果您对这些建议有任何想法,请发送电子邮件至:web-vitals-feedback@googlegroups.com!