萧伯纳说过: 一个人感到害羞的事越多,就越值得尊敬
大家好,我是柒八九。
前言
今天我们来聊聊一个比较有背景的性能指标INP
。
为何说它有背景呢,是因为它在未来,即将在更大的舞台大放异彩.
如果想了解该系列文章(浏览器底层原理&优化方案),可以参考我们已经发布的文章。如下是往期文章。
- 页面是如何生成的(宏观角度)
- Chromium 最新渲染引擎--RenderingNG
- RenderingNG中关键数据结构及其角色
- 浏览器之客户端存储
- 浏览器_知识点精讲
- 像素是怎样练成的
- 浏览器之资源获取优先级(fetchpriority)
- 浏览器之性能指标_FCP
- 浏览器之性能指标-LCP
- 浏览器之性能指标-CLS
- 浏览器之性能指标-FID
- 浏览器之性能指标-TTI
- 浏览器之性能指标-TBT
你能所学到的知识点
- 前置知识点
- 什么是INP
- 如何测量INP
- 优化INP
- INP VS FID
好了,天不早了,干点正事哇。
1. 前置知识点
前置知识点,只是做一个概念的介绍,不会做深度解释。因为,这些概念在下面文章中会有出现,为了让行文更加的顺畅,所以将本该在文内的概念解释放到前面来。如果大家对这些概念熟悉,可以直接忽略
Core Web Vitals
衡量以用户为中心
的网页性能是具有挑战性的。为了应对这一挑战,谷歌开发了一系列名为 Web Vitals
的指标。这些 Web Vitals
是衡量网页性能不同方面的信号。例如,首字节时间(TTFB
)是其中之一:从浏览器的角度来看,TTFB
衡量了请求资源和响应的第一个字节开始到达之间的时间。
Web Vitals
的子集被称为Core Web Vitals
。Core Web Vitals
被认为是需要特别关注的最关键的 Web Vitals
。因此,它们在谷歌搜索算法中扮演着一定的角色。改善网页的Core Web Vitals
可以提高在谷歌搜索中的成功率。
Core Web Vitals
目前包括三个指标:最大内容绘制时间(LCP
)、首次输入延迟(FID
)和累计布局偏移(CLS
)。它们分别衡量页面加载速度
、交互性
和视觉稳定性
。
到2024年3月
,首次输入延迟
(FID
)将被与下一次绘制的交互
(INP
)取代,成为交互性的Core Web Vitals
。
实验室数据 vs 现场数据
实验室数据(Lab Data)
在这种测试背景下,实验室数据
是通过控制页面加载,使用预先定义的一组条件来衡量的指标,通常根据设备和网络进行调整。
由于这些条件在受控环境中,它们被称为实验室环境
,这就是实验室数据
一词的由来。
现场数据(Field Data)
现场数据
,也称为RUM
(Real User Monitoring
,实际用户监测)数据,是通过监测用户在页面上的行为获得的。
现场数据基于真实用户访问 - 因此在这种情况下,我们的网站可能在实际设备上显示,需要考虑用户地理位置
以及该设备的网络条件
。
何为交互
网页上的交互始于用户输入
。然后浏览器对此输入做出反应。这包括输入延迟
、处理时间
以及在下一次绘制之前的呈现延迟
,直到新帧被呈现出来。
上图展示了一个交互流程
的生命周期。输入延迟
发生在事件处理程序开始运行之前,可能是由于主线程上的长任务
等因素引起的。然后交互的事件处理程序运行,然后在下一帧呈现之前会发生延迟。
尽管浏览器通过不依赖JavaScript
的控件(如复选框、单选按钮以及由CSS支持的控件)提供交互性,但是驱动交互性的主要因素通常还是JavaScript.
2. 什么是INP
INP
:是Interaction to Next Paint
的简写,中文名称与下一次绘制的交互
。它是一种网站性能度量指标,用于衡量用户界面的响应性,即网站对用户的交互(如点击或按键)作出反应的速度。
具体而言,它衡量的是用户交互(如点击或按键)后到下次在页面上看到视觉更新之间经过的时间。
INP 延迟
通过,上文我们得知,INP
衡量的是用户输入
(如点击
和按键
)与下一次UI更新之间经过的时间。既然有时间的考量维度,那势必就会存在影响时间长短的因素. 我们将这种因素称为延迟
.
而INP延迟
由三个组成部分构成:
- 输入延迟(
Input Delay
):等待页面上的后台任务完成,阻止事件处理程序的运行。 - 处理时间(
Processing Time
):运行JavaScript事件处理程序。 - 呈现延迟(
Presentation Dealy
):重新计算页面布局并绘制页面内容。
INP
涵盖了从鼠标、触摸或键盘输入开始,到浏览器渲染下一帧的整个时间段。
处理时间可能由多个事件处理程序组成,例如keydown
和keyup
事件。
一个交互动作可以由两个部分组成
,每个部分都有多个事件。例如,敲击键盘由keydown
、keypress
和keyup
事件组成。触摸屏幕交互包含pointerup
和pointerdown
事件。
在交互中持续时间最长的事件被选为交互的延迟。
上图是一个带有多个事件处理程序的交互示例。
交互的第一个部分
在用户按下鼠标按钮时接收输入。然而,在他们释放鼠标按钮之前
,一个帧被呈现出来。当用户释放鼠标按钮时,另一系列的事件处理程序必须运行,然后才会呈现下一个帧。
能被INP探查的用户输入
以下交互会被计入INP
:
- 鼠标点击
- 触摸(在触摸屏上)
- 键盘按键
以下交互不会被计入:
- 悬停
- 滚动
INP 得分
一个良好的INP
应该小于200毫秒
。而超过500毫秒
的INP
则被认为是较差的表现。
INP 是否测量页面上最慢的响应速度
当用户访问页面时,他们可能会点击多个不同的UI元素。
INP
将报告最慢的2%
的UI响应。通常,这意味着将报告最糟糕的延迟,但如果一个页面收到许多交互,那么只有其中最慢的一个会被报告。
例如,如果一个页面对100次交互的响应时间为50毫秒,然后有一个交互的延迟为300毫秒,那么INP
将被报告为50毫秒
。
然而,如果有三个交互都延迟了300毫秒
,那么第98百分位数
将是300毫秒,而这将被报告为INP
。
3. 如何测量INP
有了上面的理论知识,我们还需要对自己的网站有一个更加深入的了解.所以,我们就需要借助科技的力量来对目标网站进行诊断处理.
我们可以使用多种工具来测量INP
,例如:
PageSpeed Insights
在谷歌PageSpeed Insights
的真实用户部分,显示了INP
指标,用于指示页面对用户交互的响应速度。
该指标显示了第75百分位数
。例如,如果INP
为273毫秒
,这意味着对于25%
的访问者,他们所经历的输入到下一次UI更新之间最糟糕的延迟是273毫秒
。
Web Vitals
如果我们想使用JavaScript
编程来测量INP
,可以使用web-vitals库。这使我们可以从真实用户那里获取测量数据。
我们可以使用如下的script
标签加载该库:
<script src="https://unpkg.com/web-vitals@3/dist/web-vitals.iife.js">
然后,我们可以使用webVitals
对象上的onINP
方法来查看INP
值何时发生变化。
webVitals.getINP(function(info) { console.log(info) }, {reportAllChanges: true});
INP
指标的值可能会在页面的生命周期内随着用户不断与其进行交互而发生变化。reportAllChanges
确保即使是初步值也会被报告。
除了报告INP
值外,web-vitals
库还会报告一个条目列表。这些条目是导致INP
分数异常的个别用户交互。
{ "name": "pointerdown", "entryType": "first-input", "startTime": 20168.80000001192, "duration": 8, "processingStart": 20176.30000001192, "processingEnd": 20176.5, "cancelable": true, "target": "(DOM Element Object)" }
target
字段告诉我们用户与哪个页面元素进行了交互。startTime
字段是用户交互发生时的时间戳,单位为毫秒。processingStart
字段表示事件处理程序开始运行的时间。processingEnd
字段表示事件处理已完成的时间。
4. 优化INP
通过, 一些常规的检查工具,我们可以探查到页面中哪些交互是拖后腿的,此时我们就可以对这些显眼包进行特殊处理.
在前面的介绍中,我们得知,一个交互可以分为三个阶段:1. 输入延迟阶段 2:处理时间阶段 3:呈现延迟阶段.
这三个阶段的总和就是交互的总延迟。每个交互阶段都会为总交互延迟贡献一定的时间,因此重要的是要了解如何优化交互的每个部分,做到逐个击破
,使其运行时间尽量缩短。
减少CPU处理量是降低INP的最佳方法。
4.1 甄别和减少输入延迟
当用户与页面进行交互时,交互的第一个部分是输入延迟
。根据页面上的其他活动,输入延迟可能会相当长。这可能是由于主线程上发生的活动(可能是由于脚本加载、解析和编译),资源获取、定时器函数,甚至是由于快速连续发生且彼此重叠的其他交互引起的。
无论交互的输入延迟源头是什么,我们都希望将输入延迟减少到最小,以便交互能够尽快开始运行事件回调。
脚本执行与启动过程中的长任务之间的关系
在页面生命周期中,当页面加载时,首先进行渲染,但是这里有一个很迷惑的点,仅仅因为页面已经渲染出来,不意味着页面已经完成加载
。
在页面加载过程中,可能会延长输入延迟的一件事就是脚本执行
。在从网络获取了JavaScript
文件之后,浏览器在JavaScript
可以运行之前仍然需要做一些工作;这些工作包括解析脚本
以确保其语法有效,将其编译为字节码
,然后最终执行它。
如果想了解更多关于JS被解析的细节可以参考之前的文章
根据脚本的大小,这些工作可能会在主线程上引入长时间的任务,这会延迟浏览器响应其他用户交互。
减少输入延迟
避免启动过多的重复定时器
在
JavaScript
中有两个常用的定时器函数,它们可能会导致输入延迟:setTimeout
和setInterval
。
两者之间的区别在于,setTimeout
在指定的时间后安排一个回调运行。而setInterval
则在每隔n毫秒内安排一个回调持续运行,直到使用clearInterval
停止定时器。
setTimeout
本身并不是问题所在,事实上,它可以帮助避免长时间任务。然而,这取决于定时何时发生,以及当定时回调运行时,用户是否尝试与页面交互。
此外,setTimeout
可以在循环或递归中运行,这会使其更像setInterval
,尽管最好是在上一个迭代完成之前不要安排下一次迭代。虽然这意味着每次调用setTimeout
时循环会让出主线程,但我们应该确保其回调不会执行过多的工作。
setInterval
在一定时间间隔内运行一个回调,因此更有可能妨碍交互。这是因为与setTimeout
单个实例的调用不同,后者是一次性回调,可能会干扰用户交互。setInterval
的重复性质使得它更有可能干扰交互,从而增加了交互的输入延迟。
上面展示了Chrome DevTools
的性能面板的运行情况,由于第三方的setInterval
调用注册的定时器导致输入延迟,增加的输入延迟会导致交互的事件回调比本来可能的时间晚运行。
如果定时器出现在一方代码中,那么我们就可以对其进行控制。评估我们是否需要这些定时器,或者尽量减少其中的工作。然而,第三方脚本中的定时器情况就不同了。我们通常无法控制第三方脚本的行为,这样我们就需要有对第三方代码质量甄别的能力.
除了使用setTimeout
,我们还可以使用Web Worker
在单独的线程上进行CPU密集型工作
避免长任务
缓解较长输入延迟的一种方法是避免长时间任务。
正如上面的图片所示,
- 当任务过长且浏览器无法快速响应交互
- 当将较长任务分解为较小任务时,交互会被很早的执行
注意交互重叠
优化INP
的一个特别具有挑战性的方面是处理交互重叠。
交互重叠:在我们与一个元素进行交互后,在
初始交互
有机会呈现下一帧之前,我们与页面进行了另一个交互
通过Chrome
的DevTools
性能分析器我们可以看到,初始的点击交互
中的渲染工作会导致后续的键盘交互出现输入延迟。
交互重叠
的来源可能很简单,比如用户在短时间内进行了许多交互。这可能发生在用户在表单字段中输入时,许多键盘交互可能在很短的时间内发生。
如果在键盘事件上的工作特别重要,比如在自动完成字段的常见情况下,需要向后端进行网络请求,我们有几个选择:
- 考虑对输入进行
防抖
(debounce
),以限制在给定时间内事件回调执行的次数。 - 使用
AbortController
来取消发出的fetch
请求,以便主线程不会因处理fetch
回调而变得拥堵。
动画
也是导致交互重叠
而增加输入延迟的另一个来源。特别是,在JavaScript
中的动画可能会触发许多requestAnimationFrame
调用,这可能会妨碍用户交互。为了解决这个问题,尽量使用CSS动画
,以避免排队潜在的昂贵的动画帧。
避免使用原生的alert
、confirm
和prompt
对话框
像alert
这样的JavaScript
方法是向用户显示消息或要求确认操作的简单方式。然而,它们会同步运行并阻塞页面主线程,这意味着它们可见的所有时间都计入总体交互延迟。
4.2 优化事件回调
输入延迟
仅仅是INP
测量中的第一部分。我们还需要确保响应用户交互的事件回调能够尽快完成。
让主线程空闲
在优化事件回调方面,最好的一般建议是在主线程
尽量少做工作。然而,我们的交互逻辑可能很复杂,我们可能只能稍微减少它们的工作量。
车到山前,必有路.我们可以将事件回调中的工作分解为单独的任务。这可以防止集体工作成为一个长时间任务,阻塞主线程,从而允许其他本来需要在主线程上等待的交互更快地运行。
setTimeout
是一种将任务分解的方法,因为传递给它的回调会在新任务中运行。我们可以单独使用setTimeout
,也可以将其使用抽象成一个单独的函数,以便更方便地进行让步。
让步以允许呈现工作尽早进行
一种更高级的让步技术涉及将事件回调中的代码结构化,将要运行的内容限制为仅适用于为下一帧应用视觉更新所需的逻辑。其他所有内容都可以推迟到后续的任务中。
这不仅使回调保持轻巧和灵活,而且还通过不允许视觉更新在事件回调代码上阻塞来改善交互的呈现时间。
例如,想象一个富文本编辑器,它会在我们输入时格式化文本,但还会根据我们所写的内容更新UI的其他方面(例如字数、突出显示拼写错误和其他重要的视觉反馈)。此外,该应用程序还可能需要保存我们所写的内容,以便如果我们离开并返回,我们不会丢失任何工作。
在这个例子中,对用户输入的字符需要响应以下四个事项。然而,只有第一项需要在下一帧呈现之前完成。
- 使用用户输入的内容更新文本框并应用所需的格式。
- 更新显示当前字数的UI部分。
- 运行检查拼写错误的逻辑。
- 保存最近的更改(本地保存或保存到远程数据库)。
处理这些操作的代码可能如下所示:
textBox.addEventListener('input', (inputEvent) => { // 立即更新UI,这样用户所做的更改将在下一帧呈现时立即可见。 updateTextBox(inputEvent); // 使用`setTimeout`将所有其他工作推迟到至少下一帧, // 通过在`requestAnimationFrame()`回调中排队一个任务。 requestAnimationFrame(() => { setTimeout(() => { const text = textBox.textContent; updateWordCount(text); checkSpelling(text); saveChanges(text); }, 0); }); });
下图展示了将非关键的更新推迟到下一帧之后如何减少处理时间,从而减少总体交互延迟
。
避免布局抖动
布局抖动(layout thrashing
),有时也称为强制同步布局,是一种渲染性能问题,其中布局是同步进行的。
当我们在
JavaScript
中更新样式,然后在同一个任务中读取它们时,就会发生布局抖动,并且在JavaScript中有许多属性可能会引起布局抖动。
涉及布局抖动
的渲染任务将在调用堆栈的部分上方右上角用红色三角形标注,通常标有Recalculate Style
或Layout
。
布局抖动是性能瓶颈,因为在JavaScript中更新样式,然后立即请求这些样式的值,浏览器被迫执行同步布局工作,而它本来可以在事件回调完成后异步地等待稍后执行。
4.3 减少呈现延迟
交互的呈现延迟
从交互的事件回调完成运行的时刻开始,一直延伸到浏览器能够绘制下一个帧,显示出产生的视觉变化。
减小DOM大小
当页面的DOM较小时,渲染工作通常会迅速完成。然而,当DOM变得非常大时,渲染工作往往会随着DOM的增大而增加。渲染工作和DOM大小之间的关系并不是线性的,但是大型DOM确实需要比小型DOM更多的工作来进行渲染。大型DOM在以下两种情况下会引起问题:
- 在初始页面渲染期间,大型DOM需要大量工作来渲染页面的初始状态。
- 作为对用户交互的响应,大型DOM可能会导致渲染更新非常昂贵,从而增加浏览器呈现下一个帧所需的时间。
使用content-visibility来延迟渲染屏幕外的元素。
我们可以限制页面加载期间和响应用户交互期间的渲染工作量的一种方法是利用CSS的content-visibility
属性,它实际上是在元素接近视口时延迟渲染它们。虽然content-visibility
可能需要一些实践才能有效使用,但如果结果是较低的渲染时间,可以改善页面的INP
。
使用 content-visibility
属性,我们可以将元素的呈现方式设置为 auto
,这样当元素不在视口内时,其内容就会被自动懒加载
,只有当元素进入视口时,才会进行渲染。这对于包含大量元素或复杂布局的页面特别有用,因为它可以减少初始渲染所需的时间,从而提高页面的加载速度。
以下是一个使用 content-visibility
属性的示例代码:
.lazy-load { content-visibility: auto; contain-intrinsic-size: 100px 100px; /* 设置元素的默认尺寸 */ width: 100px; /* 设置元素的宽度 */ height: 100px; /* 设置元素的高度 */ }
在这个示例中,.lazy-load
类的元素将会在进入视口时才会渲染内容。
content-visibility: auto;
表示启用自动的内容懒加载。contain-intrinsic-size
属性可以设置元素的默认尺寸,这在元素还未进入视口时起到占位的作用,避免布局变化。width
和height
属性设置元素的宽度和高度.
请注意,content-visibility
并不适用于所有情况,尤其是在需要立即渲染内容的交互式页面中。在使用时,我们应该根据页面的具体情况进行测试和优化,以确保获得预期的性能提升。
在使用JavaScript渲染HTML时要注意性能成本
虽然访问任何网站的第一次都将涉及某些数量的HTML
,但常见的方法是从一个最小的初始HTML
开始,然后使用JavaScript填充内容区域。随后对内容区域的更新也会因用户交互而发生。通常将其称为单页应用(SPA
)模型。这种模式的一个缺点是,通过在客户端使用JavaScript来渲染HTML,不仅会产生用于创建该HTML的JavaScript处理的成本,而且浏览器将在解析和渲染HTML完成之前不会让步。
但是要记住,即使不是单页应用(SPA)的网站,由于交互的结果,也可能涉及通过JavaScript
进行某些数量的HTML
渲染。这通常没问题,只要我们不在客户端渲染大量的HTML,这可能会延迟下一帧的呈现。然而,重要的是要了解这种在浏览器中渲染HTML的方法的性能影响,以及如果我们通过JavaScript
渲染大量HTML
,它如何影响我们的网站对用户输入的响应能力。
5. INP VS FID
INP
与FID
之间有两个主要区别:
- FID仅测量
初始处理延迟
,而INP
测量用户输入和UI更新之间的全部时间。 - FID仅计算页面上的第一次用户交互,而INP会考虑最糟糕的延迟情况。
FID
衡量的是浏览器启动处理用户输入所需的时间。它并不包括响应事件或更新UI所花费的实际时间。
正如其名称所示,FID
仅考虑用户与页面的首次交互。尤其对于长时间保持打开的页面,比如单页应用程序,这第一次交互可能不能代表整体用户体验。
INP
通常衡量页面上最差的输入延迟。谷歌将测量用户交互延迟的第98百分位数。因此,如果页面上的INP
为250毫秒,则有2%的用户交互延迟大于250毫秒。
INP 是否是Core Web Vitals
INP
将于2024年3月成为谷歌的核心Web要素指标之一。在那时,它将取代FID
指标。
成为核心Web要素指标意味着较差的INP
可能会影响我们的谷歌排名。
后记
分享是一种态度。
参考资料:
- INP. Get ready for the new Core Web Vital
- Measure And Optimize INP
- optimize-inp
- inp
- optimize-input-delay
全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。