网站首页 > 基础教程 正文
大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!
注意:本文为译文,作者为:Annie Sullivan、Hongbo Song、Nicolás Pe?a Moreno,原文题目为《Towards a better responsiveness metric》。
1.什么是 First Input Delay
First Input Delay (FID) 指标衡量浏览器开始处理页面上的首次用户交互所需的时间,用于测量用户与设备交互的时间与浏览器实际能够开始处理事件的时间差。 FID 仅针对 tap 和按键进行测量,即仅考虑以下事件的第一次发生:
- click
- keydown
- mousedown
- pointerdown (only if it is followed by pointerup)
下图说明了 FID 的时间计算逻辑:
FID 不包括运行事件处理程序所花费的时间,也不包括浏览器随后为更新屏幕内容而完成的任何工作,其只负责测量主线程在有机会处理 Input 事件之前忙碌的时间。
这种阻塞通常是由较长时间的 JavaScript 任务执行引起的,这些任务不能随时停止,即必须在浏览器开始处理用户输入之前完成。
2.为什么选择 FID
衡量实际用户体验而非实验室数据非常重要,可以确保指标的改进为用户带来真正的好处。 FID 组成了用户决定与刚刚加载的网站进行交互时的用户体验的一部分,即捕获用户必须等待才能与站点交互响应的一些时间。 换句话说,FID 是用户交互后等待时间的下限。
其他指标,例如:总阻塞时间 (Total Blocking Time,即 TBT) 和交互时间 ( Time To Interactive,即 TTI)基于长任务,并且与 FID 一样,也测量加载期间的主线程阻塞时间。 由于这些指标可以在现场和实验室中测量,因此选择 FID 是有一定的依据。
- TTI 和 TBT 不直接衡量用户体验,这些指标都测量页面上运行的 JavaScript 数量。 虽然长时间运行的 JavaScript 确实会给网站带来问题,但如果用户没有与页面交互则不一定会影响用户体验。 即某个页面可能在 TBT 和 TTI 上得分很高,但体感很慢。 简言之,长任务和 TTI 不以用户为中心这一事实使得其很难脱颖而出。
- 实验室数据固然重要,是诊断的宝贵工具,但更重要的是用户体验。 FID 通过采用反映真实用户情况的以用户为中心的指标可以保证捕获有关体验的更有意义的信息。
值得注意的是,在现场测量真实用户的 TTI 是有问题的,因为它发生在页面加载的后期。 在计算 TTI 之前,需要 5 秒的网络安静窗口。 在实验室中,只要拥有所需的所有数据就可以选择卸载页面,但现场真实用户监控的情况并非如此。 用户可以随时选择离开页面或与之交互。 特别是,用户可能选择离开需要很长时间加载的页面,在这些情况下将不会记录准确的 TTI。 当测量 Chrome 中真实用户的 TTI 时发现,只有大约一半的页面加载达到了 TTI。
3.如何优化 FID 改进
很多时候希望开发一种新的衡量标准,扩展 FID 目前的衡量标准,同时仍保留其与用户体验的紧密联系。比如:
- 捕获每个事件的完整持续时间(而不仅仅是延迟)
- 将作为同一逻辑用户交互的一部分事件分组在一起,并将该交互的延迟定义为其所有事件的最大持续时间
- 考虑所有用户输入的响应能力(不仅仅是第一个)
- 为页面上发生的所有交互创建一个聚合分数,贯穿整个生命周期
如果网站在这些指标上得分很低,则未对用户交互做出快速响应。
3.1 捕获完整的事件持续时间
第一个明显的改进是尝试捕获事件的更广泛的端到端延迟。 如上所述,FID 仅捕获输入事件的延迟部分,没有考虑浏览器实际处理事件所需的时间。
事件的生命周期有多个阶段,如下图所示:
以下是 Chrome 处理 Input 事件所采取的步骤:
- 用户的输入事件发生,event 对象的 timeStamp 标记了时间
- 浏览器执行命中测试来确定事件属于哪个 HTML 框架(主框架或 iframe),浏览器将事件发送到负责该 HTML 帧的渲染器进程
- 渲染器接收事件并将其排队,以便适时处理
- 渲染器运行事件处理程序,可以对额外的异步工作进行排队,例如: setTimeout 和 fetch,它们是输入处理的一部分。 到这里,同步工作就完成了。
- 屏幕上绘制了帧数据反映事件处理程序运行的结果。 请注意:事件处理程序排队的任何异步任务可能仍未完成。
步骤 (1) 和 (3) 之间的时间是事件的延迟,这是 FID 测量的。步骤 (1) 和 (5) 之间的时间是事件的持续时间,即新指标要衡量的内容。
// 以下示例记录持续时间大于 0 的所有观察到的性能数据
function perfObserver(list, observer) {
list.getEntries().forEach((entry) => {
if (entry.duration > 0) {
console.log(`${entry.name}'s duration: ${entry.duration}`);
}
});
}
const observer = new PerformanceObserver(perfObserver);
observer.observe({ entryTypes: ["measure", "mark", "resource"] });
事件的持续时间包括:延迟、事件处理程序中发生的工作以及浏览器在这些处理程序运行后绘制下一帧所需执行的工作。 当前,事件的持续时间可通过 Event Timing API 的 duration 属性获得。
3.2 将事件分组为交互
将指标测量从 delay 扩展到 duration 是第一步,但仍然在指标中留下了一个关键差距:即关注的是单个事件,而不是与页面交互的整体用户体验。
单个用户交互可能会触发诸多不同事件,并且单独测量每个事件并不能清晰地了解整体用户体验。 为了确保指标尽可能准确地捕获用户在点击、按键、滚动和拖动时等待响应的全部时间,引入交互的概念来测量每个交互的延迟。
下表列出了想要定义的四种交互以及与其关联的 DOM 事件。 请注意,与发生此类用户交互时调度的所有事件的集合并不完全相同。 例如,当用户滚动时,会调度滚动事件,但是在屏幕更新以反映滚动之后发生的事情则不将其视为交互延迟的一部分。
上图列出的前三种交互(键盘、点击和拖动)当前由 FID 涵盖。 对于新的响应度指标还应该包括滚动,因为滚动在 Web 非常常见,并且是页面对用户的响应速度的一个关键方面。
键盘
键盘交互有两个部分,即用户按下和释放按键,用户交互涉及三个相关事件:keydown、keyup 和 keypress。 下图说明了键盘交互的 keydown 和 keyup 延迟和持续时间 duration:
在上图中,duration 是不相交的,因为 keydown 更新的帧是在 keyup 发生之前渲染的,但情况并不总是如此。 此外,请注意,帧可以在渲染器进程中的任务中间渲染,因为生成帧所需的最后步骤是在渲染器进程之外完成的。
eventTarget.addEventListener("keydown", (event) => {
// keydown为事件名称,也支持keyup、keypress
if (event.isComposing || event.keyCode === 229) {
return;
}
// do something
});
keydown 和 keypress 发生在用户按下按键时,而 keyup 发生在用户释放按键时。 通常,主要内容更新发生在按下按键时,如:屏幕上出现文本或者应用修饰符效果。 也就是说,希望捕获更罕见的情况,其中 keyup 也会渲染 UI 更新,因此希望了解所花费的总时间。
为了捕获键盘交互所花费的总时间,可以计算 keydown 和 keyup 事件的持续时间的最大值。
Tap
另一个重要的用户交互是用户点击或 Tap 网站时。 与按键类似,一些事件在用户按下时触发,另一些事件在用户释放时触发,如上图所示,请注意,与点击相关的事件在桌面和移动设备上略有不同。
function startup() {
const el = document.getElementById("canvas");
el.addEventListener("touchstart", handleStart);
el.addEventListener("touchend", handleEnd);
el.addEventListener("touchcancel", handleCancel);
el.addEventListener("touchmove", handleMove);
log("Initialized.");
}
document.addEventListener("DOMContentLoaded", startup);
对于点击或单击,释放(release)通常是触发大多数事件的地方,但是,与键盘交互一样希望捕获完整的交互。 在这种情况下,这样做更为重要,因为在点击时进行一些 UI 更新实际上并不罕见。
因此希望能包含所有这些事件的持续时间,但由于其中许多事件完全重叠,因此只需测量指针向下、指针向上和单击即可覆盖完整的交互。
drag
drag 具有类似的关联事件并且通常会导致站点的重要 UI 更新, 但对于指标,则只考虑拖动开始和拖动结束,即拖动的初始和最终状态。
下面是拖拽事件的使用实例:
<script>
function dragstartHandler(ev) {
// Add the target element's id to the data transfer object
ev.dataTransfer.setData("text/plain", ev.target.id);
}
window.addEventListener("DOMContentLoaded", () => {
// Get the element by id
const element = document.getElementById("p1");
// Add the ondragstart event listener
element.addEventListener("dragstart", dragstartHandler);
});
</script>
<p id="p1" draggable="true">This element is draggable.</p>
这是为了更容易推理并使延迟与所考虑的其他交互具有可比性,与排除鼠标悬停等连续事件的决定是一致的。
该事件也不考虑通过拖放 API 实现拖动,因为其仅适用于桌面。
scroll
与网站交互的最常见形式之一是通过滚动。 新指标希望测量用户初始滚动交互的延迟。 特别是浏览器对用户请求滚动这一事实的初始反应,即该指标不会涵盖整个滚动体验。 也就是说,滚动会产生许多帧,需要把注意力集中在作为对滚动的反应而产生的初始帧上。
那为什么是初始帧?
- 其一,后续帧可以通过单独的平滑度来捕获。 也就是说,一旦向用户展示了第一个滚动结果,就应该根据滚动体验的流畅程度来衡量其余结果。 因此,平滑度可以更好地体现这一点。
- 与 FID 一样,选择坚持离散的用户体验,即具有与之相关的明确时间点的用户体验,并且可以轻松计算其延迟。 滚动作为一个整体是一种持续的体验,所以不打算用这个指标来衡量所有的体验。
那么为何要测量 scrolls 呢?
在 Chrome 中收集的滚动性能表明滚动通常非常快。 也就是说,出于各种原因仍然希望在新指标中包含初始滚动延迟。
- 首先,滚动之所以快只是因为已经被优化了很多,但网站仍然有办法绕过浏览器提供的一些性能提升。 Chrome 中最常见的一种是强制滚动在主线程上发生。 因此,指标应该能够说明这种情况何时发生并导致用户滚动性能不佳。
- 其次,滚动非常重要,不容忽视。 如果排除滚动,那么将有一个很大的盲点,并且滚动性能可能会随着时间的推移而下降,而 Web 开发人员却没有正确注意到。
用户滚动时会调度多个事件,例如: touchstart、touchmove 和 scroll。 除了滚动事件之外,很大程度上取决于用于滚动的设备:在移动设备上用手指滚动时调度触摸事件,而用鼠标滚轮滚动时发生滚轮事件。
初始滚动完成后会触发滚动事件。 一般来说,没有 DOM 事件会阻止滚动,除非网站使用非被动事件侦听器(Non-passive Event Listeners)。 因此认为滚动与 DOM 事件完全分离,想要测量的是从用户移动到足以产生滚动手势到显示滚动发生的第一帧的时间。
document.addEventListener('touchstart', onTouchStart, { passive: true });
3.3 如何定义交互的延迟
具有“向下”和“向上”的交互需要单独考虑,以避免归因于用户按住手指所花费的时间。
对于这些类型的交互,希望延迟涉及与其相关的所有事件的持续时间。 由于交互的每个“向下”和“向上”部分的事件持续时间可以重叠,因此实现此目的的交互延迟的最简单定义是与其关联的任何事件的最大持续时间。
keydown 和 keyup 持续时间也可能重叠。例如,当两个事件渲染的帧相同时,可能会发生这种情况,如下图所示:
这种最大限度使用的方法有利有弊:
- 优点 1:与打算测量滚动的方式一致,因为只测量单个持续时间值。
- 优点 2:目的是减少键盘交互等情况下的噪音,在这种情况下,按键通常不执行任何操作,并且用户可能会快速或缓慢地按下和释放按键。
- 缺点:不会捕获用户的完整等待时间。 例如:将捕获拖动的开始或结束,但不能同时捕获两者。
对于滚动(只有一个关联事件),希望将其延迟定义为浏览器因滚动而生成第一帧所需的时间。 也就是说,延迟是足够大以触发滚动的第一个 DOM 事件(如 touchmove,如果使用手指)的时间戳与反映滚动发生的第一个绘制之间的增量。
3.4 聚合每个页面的所有交互
一旦定义了交互的延迟就需要计算页面加载的聚合值,该页面加载可能有许多用户交互。 聚合价值可以做到:
- 形成与业务指标的相关性
- 评估与其他性能指标的相关性。 理想情况下,新指标将足够独立,从而为现有指标增加价值
- 以易于理解的方式轻松公开工具中的价值
为了执行此聚合需要解决两个问题:
- 汇总哪些数字?
- 如何汇总数字?
目前正在探索和评估几种选择:
- 一种选择是定义交互延迟的平衡,可能取决于类型(滚动、键盘、点击或拖动)。 例如:如果点击的时间为 100 毫秒,点击的延迟为 150 毫秒,则该交互超出为 50 毫秒。 然后可以计算页面中任何用户交互超出 50ms 的最大延迟量。
- 另一种选择是计算整个页面生命周期中交互的平均或中值延迟。 因此,如果延迟为 80 毫秒、90 毫秒和 100 毫秒,那么页面的平均延迟将为 90 毫秒。 还可以考虑平均数或中位数“超出”,以根据交互类型考虑不同的期望。
4.在 Web 性能 API 上看起来如何
4.1 事件计时(Event Timing)缺少什么
遗憾的是,并非本文提出的所有想法都可以使用 Event Timing API 来捕获。 特别是,没有简单的方法可以了解与给定用户与 API 交互相关的事件。 为此,建议向 API 添加交互 ID。
Event Timing API 的另一个缺点是无法测量滚动交互,因此正在努力启用这些测量,如:通过事件计时或单独的 API。
4.2 现在可以尝试什么
目前,可以计算点击/拖动和键盘交互的最大延迟。 以下代码片段将产生这两个指标。
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.
参考资料
https://web.dev/articles/better-responsiveness-metric
https://developer.chrome.com/docs/lighthouse/best-practices/uses-passive-event-listeners/
https://www.foundsm.com/blog/core-web-vitals-an-introduction-to-googles-new-page-experience-signal/
https://developer.mozilla.org/en-US/docs/Web/API/PerformanceEntry/duration
https://docs.google.com/presentation/d/1VwGIzypntWNosCTXWMsUI6ifw4sEKSRQgnwx3P_wqVg/edit?pli=1#slide=id.ga39e0eb0f2_0_2
https://developer.mozilla.org/en-US/docs/Web/API/Element/keydown_event
https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API
猜你喜欢
- 2024-10-22 一招儿轻松破解网页禁止复制限制,想Copy什么你说的算
- 2024-10-22 吉村Yoshimura迎来70周年,推出一系列全新改装零件迈入新时代
- 2024-10-22 《魔兽世界》10.0前夕UI自定义技巧
- 2024-10-22 邮箱收件人组件成长历程(三)跨栏拖拽不同数据方案对比
- 2024-10-22 看完就懂的前端拖拽那些事 前端实现拖拽排序
- 2024-10-22 网页禁止复制粘贴怎么解决教程 网页内容禁止复制怎么办
- 2024-10-22 MIT与谷歌最新研究DragGAN:无损图像精确控制技术的重大突破
- 2024-10-22 沉湎于虚幻的梦想而忘记现实的生活,是毫无益处的
- 2024-10-22 一招儿轻松破解网页禁止复制限制,想Copy什么你说的算!
- 2024-10-22 别错过!一款不错的轻量级拖拽库 drag-kit,支持React、Vue等
- 最近发表
- 标签列表
-
- jsp (69)
- pythonlist (60)
- gitpush (78)
- gitreset (66)
- python字典 (67)
- dockercp (63)
- gitclone命令 (63)
- dockersave (62)
- linux命令大全 (65)
- mysql教程 (60)
- pythonif (68)
- pythonifelse (59)
- deletesql (62)
- c++模板 (62)
- linuxgzip (68)
- 字符串连接 (73)
- nginx配置文件详解 (61)
- html标签 (69)
- c++初始化列表 (64)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- console.table (62)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)