了解我们对衡量响应速度的想法,并向我们提供反馈。
在 Chrome Speed Metrics 团队中,我们正在努力加深对网页响应用户输入速度的理解。我们想分享一些改进响应速度指标的想法,并听取您的反馈。
这篇文章将涵盖两个主要主题
- 回顾我们当前的响应速度指标 First Input Delay (FID),并解释为什么我们选择 FID 而不是其他一些替代方案。
- 介绍我们一直在考虑的一些改进,这些改进应该更好地捕捉单个事件的端到端延迟。这些改进还旨在更全面地了解页面在其整个生命周期内的整体响应速度。
什么是 First Input Delay?
First Input Delay (FID) 指标衡量浏览器开始处理页面上的第一次用户交互所需的时间。特别是,它衡量用户与设备交互的时间与浏览器实际能够开始处理事件处理程序的时间之间的差异。FID 仅针对点击和按键进行衡量,这意味着它仅考虑以下事件的首次发生
click
keydown
mousedown
pointerdown
(仅当后面跟着pointerup
时)
下图说明了 FID
FID 不包括运行这些事件处理程序所花费的时间,也不包括浏览器之后为更新屏幕所做的任何工作。它衡量的是主线程在有机会处理输入之前处于繁忙状态的时间量。这种阻塞时间通常是由长时间运行的 JavaScript 任务引起的,因为这些任务不能随时停止,因此必须完成当前任务,浏览器才能开始处理输入。
为什么我们选择 FID?
我们认为衡量实际用户体验非常重要,以确保指标的改进能够为用户带来实际利益。我们选择衡量 FID,因为它代表了用户决定与刚刚加载的网站进行交互时的用户体验部分。FID 捕捉了用户必须等待才能看到其与网站交互的响应的部分时间。换句话说,FID 是用户在交互后等待的时间量的下限。
其他指标,如Total Blocking Time (TBT) 和 Time To Interactive (TTI) 基于 long tasks,并且与 FID 一样,也衡量加载期间的主线程阻塞时间。由于这些指标可以在现场和实验室中进行衡量,因此许多开发人员询问为什么我们不优先选择其中一个而不是 FID。
这有几个原因。也许最重要的原因是这些指标没有直接衡量用户体验。所有这些指标都衡量页面上运行了多少 JavaScript。虽然长时间运行的 JavaScript 确实容易给网站带来问题,但如果用户在这些任务发生时没有与页面交互,则这些任务不一定会影响用户体验。一个页面在 TBT 和 TTI 上可能得分很高,但感觉很慢,或者得分很差,但用户感觉很快。根据我们的经验,这些间接测量导致某些网站的指标效果很好,但对大多数网站的效果不佳。简而言之,long tasks 和 TTI 不是以用户为中心的事实使得这些指标成为较弱的候选指标。
虽然 实验室测量 当然很重要,并且是诊断的宝贵工具,但真正重要的是用户如何体验网站。通过拥有一个反映真实用户情况的以用户为中心的指标,您可以保证捕捉到有关体验的一些有意义的信息。我们决定从该体验的一小部分开始,即使我们知道这一部分不能代表完整的体验。这就是为什么我们正在努力捕捉用户等待其输入得到处理的更大部分时间。
在现场对真实用户测量 TTI 是有问题的,因为它发生在页面加载的后期。甚至在计算 TTI 之前,都需要 5 秒的网络安静窗口。在实验室中,您可以选择在拥有所需的所有数据时卸载页面,但在现场的真实用户监控中并非如此。用户可以选择随时离开页面或与之交互。特别是,用户可能会选择离开加载时间过长的页面,并且在这些情况下不会记录准确的 TTI。当我们在 Chrome 中测量真实用户的 TTI 时,我们发现只有大约一半的页面加载达到了 TTI。
我们正在考虑哪些改进?
我们希望开发一种新的指标,该指标扩展了 FID 今天衡量的范围,但仍然保留其与用户体验的紧密联系。
我们希望新的指标能够
- 考虑所有用户输入的响应速度(不仅仅是第一个)
- 捕捉每个事件的完整持续时间(不仅仅是延迟)。
- 将作为同一逻辑用户交互的一部分发生的事件分组在一起,并将该交互的延迟定义为其所有事件的最大持续时间。
- 为页面上发生的所有交互创建一个聚合分数,贯穿其整个生命周期。
为了取得成功,我们应该能够非常有信心地说,如果一个网站在这个新指标上得分很差,那么它对用户交互的响应速度不够快。
捕捉完整的事件持续时间
第一个明显的改进是尝试捕捉更广泛的事件端到端延迟。如上所述,FID 仅捕捉输入事件的延迟部分。它不考虑浏览器实际处理事件处理程序所花费的时间。
事件的生命周期中有多个阶段,如下图所示
以下是 Chrome 处理输入的步骤
- 用户输入发生。发生的时间是事件的
timeStamp
。 - 浏览器执行命中测试,以确定事件属于哪个 HTML 框架(主框架或某些 iframe)。然后,浏览器将事件发送到负责该 HTML 框架的相应渲染器进程。
- 渲染器接收事件并将其排队,以便它可以在可用时进行处理。
- 渲染器通过运行其处理程序来处理事件。这些处理程序可能会将额外的异步工作排队,例如
setTimeout
和 fetches,这些工作是输入处理的一部分。但在这一点上,同步工作已完成。 - 绘制一个帧到屏幕上,该帧反映了事件处理程序运行的结果。请注意,事件处理程序排队的任何异步任务可能仍未完成。
上述步骤 (1) 和 (3) 之间的时间是事件的延迟,这正是 FID 衡量的内容。
上述步骤 (1) 和 (5) 之间的时间是事件的持续时间。这将是我们新指标将要衡量的内容。
事件的持续时间包括延迟,但也包括事件处理程序中发生的工作以及浏览器在这些处理程序运行后需要完成的绘制下一帧的工作。事件的持续时间当前在 Event Timing API 中通过条目的 duration 属性提供。
理想情况下,我们也希望捕捉由事件触发的异步工作。但问题是,由事件触发的异步工作的定义极其难以正确理解。例如,开发人员可以选择在事件处理程序上开始一些动画,并使用 setTimeout
来开始此类动画。如果我们捕捉到处理程序上发布的所有任务,动画将延迟完成时间,只要动画运行。我们认为值得研究如何使用启发式方法来捕捉异步工作,这些工作应该尽快完成。但是,我们在这样做时要非常小心,因为我们不想惩罚旨在长时间完成的工作。因此,我们的初步工作将着眼于步骤 5 作为终点:它将仅考虑同步工作以及在此类工作完成后绘制所需的时间量。也就是说,在我们的初步工作中,我们不会应用启发式方法来猜测将在步骤 4 中异步启动的工作。
值得注意的是,在许多情况下,工作应该同步执行。事实上,这可能是不可避免的,因为事件有时会一个接一个地分派,并且事件处理程序需要按顺序执行。也就是说,我们仍然会错过重要的工作,例如触发获取的事件或依赖于在下一个 requestAnimationFrame
回调中完成的重要工作。
将事件分组为交互
将指标测量从延迟扩展到持续时间是一个好的第一步,但它仍然在指标中留下了一个关键的空白:它侧重于单个事件,而不是与页面交互的用户体验。
许多不同的事件可以作为单个用户交互的结果触发,并且单独测量每个事件无法清晰地了解用户体验。我们希望确保我们的指标尽可能准确地捕捉用户在点击、按键、滚动和拖动时必须等待响应的完整时间量。因此,我们引入了交互的概念来衡量每个交互的延迟。
交互类型
下表列出了我们想要定义的四种交互以及它们关联的 DOM 事件。请注意,这与发生此类用户交互时分派的所有事件的集合不完全相同。例如,当用户滚动时,会分派一个滚动事件,但它发生在屏幕已更新以反映滚动之后,因此我们不将其视为交互延迟的一部分。
交互 | 开始/结束 | 桌面事件 | 移动事件 |
---|---|---|---|
键盘 | 按下按键 | keydown |
keydown |
keypress |
keypress |
||
释放按键 | keyup |
keyup |
|
点击或拖动 | 点击开始或拖动开始 | pointerdown |
pointerdown |
mousedown |
touchstart |
||
点击抬起或拖动结束 | pointerup |
pointerup |
|
mouseup |
touchend |
||
click |
mousedown |
||
mouseup |
|||
click |
|||
滚动 | 不适用 |
上面列出的前三种交互(键盘、点击和拖动)当前包含在 FID 中。对于我们的新响应速度指标,我们希望也包括滚动,因为滚动在 Web 上非常常见,并且是页面对用户感觉有多灵敏的关键方面。
请注意,这些交互中的每一个都包含两个部分:用户按下鼠标、手指或按键时以及用户抬起鼠标、手指或按键时。我们需要确保我们的指标不会将用户在两个操作之间按住手指的时间计入页面延迟的一部分!
键盘
键盘交互包含两个部分:用户按下按键时和用户释放按键时。与此用户交互关联的事件有三个:keydown
、keyup
和 keypress
。下图说明了键盘交互的 keydown
和 keyup
延迟和持续时间
在上图中,持续时间是不相交的,因为来自 keydown
更新的帧在 keyup
发生之前呈现,但这并不总是需要如此。此外,请注意,帧可以在渲染器进程中的任务中间呈现,因为生成帧所需的最后步骤是在渲染器进程外部完成的。
keydown
和 keypress
在用户按下按键时发生,而 keyup
在用户释放按键时发生。通常,主要内容更新在按下按键时发生:文本出现在屏幕上,或者应用了修饰符效果。也就是说,我们想要捕捉 keyup
也会呈现有趣的 UI 更新的更罕见的情况,因此我们想要查看花费的总时间。
为了捕捉键盘交互所花费的总时间,我们可以计算 keydown
和 keyup
事件的持续时间的最大值。
这里有一个值得一提的边缘情况:在某些情况下,用户可能会按下按键并花费一段时间才释放它。在这种情况下,分派的事件序列可能 vary。在这些情况下,我们将认为每个 keydown
都有一个交互,该交互可能具有也可能没有对应的 keyup
。
点击
另一个重要的用户交互是用户点击或单击网站时。与 keypress
类似,当用户按下时会触发一些事件,当用户释放时会触发另一些事件,如上图所示。请注意,与点击关联的事件在桌面与移动设备上略有不同。
对于点击或单击,释放通常是触发大多数反应的事件,但与键盘交互一样,我们希望捕捉完整的交互。在这种情况下,这样做更重要,因为在点击按下时进行一些 UI 更新实际上并不罕见。
我们希望包括所有这些事件的事件持续时间,但由于其中许多事件完全重叠,我们需要仅测量 pointerdown
、pointerup
和 click
以涵盖完整的交互。
pointerdown
和 pointerup
吗?最初的一个想法是使用 pointerdown
和 pointerup
事件,并假设它们涵盖了我们感兴趣的所有持续时间。可悲的是,事实并非如此,正如这个 边缘情况 所示。尝试在移动设备上或使用移动设备模拟打开此网站,然后点击标有“Click me”的位置。此网站触发了 浏览器点击延迟。可以看出,pointerdown
、pointerup
和 touchend
很快分派,而 mousedown
、mouseup
和 click
则等待延迟后再分派。这意味着,如果我们仅查看 pointerdown
和 pointerup
,那么我们将错过来自合成事件的持续时间,由于浏览器点击延迟,该持续时间很大,并且应该包括在内。因此,我们应该测量 pointerdown
、pointerup
和 click
以涵盖完整的交互。
拖动
我们决定也包括拖动,因为它具有类似的关联事件,并且因为它通常会导致网站的重要 UI 更新。但对于我们的指标,我们打算仅考虑拖动开始和拖动结束 - 拖动的初始和最终部分。这是为了更容易推理,并使延迟与其他考虑的交互具有可比性。这与我们排除连续事件(如 mouseover
)的决定是一致的。
我们也没有考虑通过 Drag and Drop API 实现的拖动,因为它们仅在桌面上工作。
滚动
与网站交互的最常见形式之一是通过滚动。对于我们的新指标,我们希望衡量用户初始滚动交互的延迟。特别是,我们关心浏览器对用户请求滚动这一事实的初始反应。我们将不涵盖整个滚动体验。也就是说,滚动会产生许多帧,我们将注意力集中在作为对滚动的反应而产生的初始帧上。
为什么只关注第一个帧?首先,后续帧可能会被单独的平滑度 提案 捕获。也就是说,一旦向用户显示了滚动的第一个结果,其余部分应根据滚动体验的平滑程度来衡量。因此,我们认为平滑度工作可以更好地捕捉到这一点。因此,与 FID 一样,我们选择坚持离散的用户体验:具有与之关联的明确时间点的用户体验,并且我们可以轻松计算其延迟。滚动作为一个整体是一种连续的体验,因此我们不打算在此指标中衡量所有滚动。
那么为什么要衡量滚动呢?我们在 Chrome 中收集的滚动性能表明,滚动通常非常快。也就是说,我们仍然希望将初始滚动延迟包括在我们的新指标中,原因有很多。首先,滚动速度很快,仅仅是因为它经过了如此多的优化,因为它非常重要。但网站仍然可以通过多种方式绕过浏览器提供的一些性能提升。Chrome 中最常见的一种是强制滚动在主线程上发生。因此,我们的指标应该能够说明这种情况何时发生并导致用户的滚动性能不佳。其次,滚动太重要了,不容忽视。我们担心,如果我们排除滚动,那么我们将有一个很大的盲点,并且滚动性能可能会随着时间的推移而下降,而 Web 开发人员却没有适当注意到。
当用户滚动时,会分派几个事件,例如 touchstart
、touchmove
和 scroll
。除了滚动事件之外,这在很大程度上取决于用于滚动的设备:当在移动设备上用手指滚动时,会分派触摸事件,而当使用鼠标滚轮滚动时,会发生滚轮事件。滚动事件在初始滚动完成后触发。总的来说,除非网站使用 非被动事件侦听器,否则没有 DOM 事件会阻止滚动。因此,我们将滚动视为与 DOM 事件完全分离。我们想要衡量的是从用户移动足够大的距离以产生滚动手势到显示滚动发生的第一个帧之间的时间。
如何定义交互的延迟?
正如我们上面指出的,具有“按下”和“抬起”组件的交互需要单独考虑,以避免将用户按住手指的时间归因于延迟。
对于这些类型的交互,我们希望延迟包括与其关联的所有事件的持续时间。由于交互的每个“按下”和“抬起”部分的事件持续时间可能会重叠,因此实现此目的的最简单的交互延迟定义是与其关联的任何事件的最大持续时间。回顾前面的键盘图,这将是 keydown
持续时间,因为它比 keyup
更长
keydown
和 keyup
持续时间也可能重叠。例如,当为两个事件呈现的帧相同时,就会发生这种情况,如下图所示
使用最大值的方法有利有弊,我们有兴趣听取您的反馈
- 优点:它与我们打算衡量滚动的方式一致,因为它仅衡量单个持续时间值。
- 优点:它旨在减少键盘交互等情况的噪音,在这些情况下,
keyup
通常不执行任何操作,并且用户可能会快速或缓慢地执行按键和释放。 - 缺点:它没有捕捉到用户的完整等待时间。例如,它将捕捉拖动的开始或结束,但不会同时捕捉两者。
对于滚动(只有一个关联事件),我们希望将其延迟定义为浏览器生成作为滚动结果的第一个帧所需的时间。也就是说,延迟是第一个 DOM 事件(如 touchmove
,如果使用手指)的事件 timeStamp
与反映滚动发生的第一次绘制之间的时间差。
聚合每个页面的所有交互
一旦我们定义了交互的延迟是什么,我们就需要计算页面加载的聚合值,页面加载可能具有许多用户交互。拥有聚合值使我们能够
- 形成与业务指标的关联。
- 评估与其他性能指标的关联。理想情况下,我们的新指标将足够独立,可以为现有指标增加价值。
- 以易于理解的方式轻松地在工具中公开值。
为了执行此聚合,我们需要解决两个问题
- 我们尝试聚合哪些数字?
- 我们如何聚合这些数字?
我们正在探索和评估几种选项。我们欢迎您对这种聚合的想法。
一种选择是为交互的延迟定义预算,这可能取决于类型(滚动、键盘、点击或拖动)。例如,如果点击的预算为 100 毫秒,而点击的延迟为 150 毫秒,那么该交互的超预算量将为 50 毫秒。然后,我们可以计算页面中任何用户交互的最大延迟量。
另一种选择是计算页面生命周期内交互的平均或中值延迟。因此,如果我们有 80 毫秒、90 毫秒和 100 毫秒的延迟,则页面的平均延迟将为 90 毫秒。我们还可以考虑平均或中值“超预算”,以考虑取决于交互类型的不同期望。
这在 Web Performance 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,让我们知道您对这些想法的看法!