作者:Milica Mihajlija 原文:https://calibreapp.com/blog/new-generation-of-performance-metrics 翻译 / 润色:单是昊 来源公众号:ByteDance Web Infra 这是我们 Web Infra APM 团队体验监控相关系列的第一篇文章,后续会持续更新这个专题。记得先加个关注,不迷路。
传统的性能指标如 load time[1] 或 DOMContentLoaded[2] 专注于容易衡量的技术细节,但是它们很难反应出用户所真正关心的是什么。如果你仅仅是把加载速度优化的更快,你很快就会发现网站的用户体验依然很差。一个站点的总加载时间可以很快,但如果它直到所有内容都准备好了才渲染的话,用户只能盯着空白的屏幕一段时间。如果点击了按钮但没有反应,是因为主线程被 JavaScript 任务占满而阻塞了,此时虽然页面已经“加载”,但用户依然会感到沮丧。
和上面的例子相比,这个页面有更长的总加载时间,但是渐进式的渲染内容并且不用过度的 JavaScript 来阻塞主线程。用户体验会更好,但加载时间上却无法反映。
这就是创建用户为中心的性能指标的原因,它们专注于用户视角下的浏览体验。
以用户为中心的性能指标衡量页面显示有用内容的速度,
用户是否可以与之交互,以及这些交互是否流畅且无延迟。
用户对页面是否“快”的看法,会受到加载体验中不同时刻的影响,所以这里有几个指标试图捕捉这一点:
用户体验 | 指标 |
---|---|
发生了吗? | First Paint (FP), First Contentful Paint (FCP) |
内容有用吗? | First Meaningful Paint (FMP), Speed Index (SI) |
内容可用吗? | Time to Interactive (TTI) |
这些指标让我们离衡量用户体验又近了一步,但都不是完美的,业界持续研究和开发用来描述良好用户体验的关键指标。随着对于用户体验的理解的深入和测量能力的增强,指标也同样需要随之进化。
这个过程的结果是三个全新的性能指标,它们填补了用户体验故事中的空白。
- Largest Contentful Paint (LCP)
- Total Blocking Time (TBT)
- Cumulative Layout Shift (CLS)
Largest Contentful Paint
Largest Contentful Paint (LCP) 意味着
最大的内容在可视区域内变得可见的时间点
最大的元素,例如一篇文章中的一大段文字或产品页面上的一张图片,大概就是让你理解页面内容的最有用的元素。经过测试[3],LCP 非常近似于页面主要内容加载的时间点。
在上面的例子中,LCP 大概在 0.8ms 也就是香蕉图片加载完时发生。
为了优化 LCP,确保:
- 消除阻塞渲染的资源[4]。
- 最小化关键请求链[5],通过以正确的优先级和顺序加载资源。
- 压缩图像,并为不同的设备提供不同的图像大小。
- 优化 CSS,压缩文件和提取关键 CSS[6]。
- 使用字体加载策略[7]来避免了不可见文字的闪烁(FOIT)。
为什么我们需要 LCP
浏览器和性能监控工具已经报告绘制指标很长一段时间了。我们的目标一直是衡量用户感知页面加载进度的关键时刻。然而,其中的一些指标据我们所知,有一些明显的缺陷:
指标 | 定义 | 问题 |
---|---|---|
First Contentful Paint (FCP) | 浏览器绘制第一块 DOM 内容的时间点。 | - 通常和用户无关(例如加载指示器或者进度条) |
First Meaningful Paint (FMP) | 页面加载中产生最剧烈的布局变化后的绘制时间点。 | - 非标准化并且难以在浏览器之间统一实现。- 约 20% 的情况下不准确。" |
Speed Index (SI) | Speed Index (SI) | - 复杂的指标,难以解释。- 计算密集,所以在主流浏览器中都不可用于真实用户监控(RUM)。" |
LCP 则不同:
- 容易理解。
- 相关的(给出与SI相似的结果)
- 在 RUM 工具中容易计算和上报。
事实上 LCP 的出现并不意味着其他指标就是没用的。FCP 依然是相关的,因为它给用户页面实际加载的反馈。关于 FMP 的实验则对 LCP 的发展提供了有价值的见解。
最大内容绘制在 Calibre[8](一个性能监控平台)、Chrome DevTools 或通过 Largest Contentful Paint API[9] 都可以使用。在 Lighthouse(从 6.0 版本开始)中 LCP 会被用来计算性能得分。如果想要学习更多有关如何计算得分、和前一个版本相比有何变化的话,请查看性能得分计算器[10]。
Total Blocking Time
Total Blocking Time(TBT) 描述了 JavaScript 主线程活动。
它有助于理解在加载期间,页面无法响应用户输入的时间有多久。
在多数情况下,所有渲染网页[11]的工作、运行 JavaScript 和响应用户输入,都发生在主线程上。当用户在主线程正在处理其他任务时点击了按钮,则响应将被延迟,直到主线程空闲。如果延迟很小,比如小于 50ms 的话,用户甚至不会注意到。如果主线程阻塞更久的话,用户则会感受到页面的的延迟和未响应。
TBT 量化了主线程花费在长任务[12]上的时间,以估计长任务在页面加载过程的过程中潜在的影响用户交互的风险。
什么是长任务?
如果一个任务在主线程上运行超过 50 毫秒,那么它就是长任务。超过 50ms 后的任务耗时,都算作任务的阻塞时间。
一个页面的 Total Blocking Time 总阻塞时间,是从 FCP 到 TTI (Time to Interactive 可交互时间)之间所有长任务的阻塞时间的总和。
下表是上述例子中,TBT 的计算方式:
Task | 任务时间 | 任务阻塞时间(> 50 ms) |
---|---|---|
1 | 75ms | 25ms |
2 | 25ms | 0ms |
3 | 85ms | 35ms |
4 | 30ms | 0ms |
Total Blocking Time | 0ms |
跟踪 TBT 是改善页面交互性的第一步。如果你注意到 TBT 值过高:
- 对 JavaScript bundle 进行代码分割,并延迟加载那些对初始加载不重要的包。
- 可能的话,将代码分解成工作更少、执行更快的函数。
- 减少频繁的 DOM 查询。
- 将计算密集型任务交给 Service workers 或 Web workers。
Total Blocking Time 和 Time To Interactive 的区别?
Time to Interactive (TTI)[13] 可交互时间衡量页面何时可以可靠的响应用户的输入。如果页面的主线程上至少 5 秒都没有长任务,那么可以认为它是“完全可交互的”。
Total Blocking Time 总阻塞时间,只在 FCP 和 TTI 之间计算。
TTI 识别主线程何时变为空闲状态。
TBT 量化主线程在空闲之前的繁忙程度。
TTI 有时可能会误导用户,但当与 TBT 结合使用时,您就会更清楚地了解页面对用户输入的响应程度。
例如,两个页面可以有相同的TTI,但如果其中一个页面将工作分解成更小的任务,而另一个页面的主线程上一直占满了一个很长的任务,那么第二种情况下的用户体验将会更差。这种差异将反映为高得多的 TBT,如下图所示:
在 Calibre 和 Lighthouse 中都有Total Blocking Time 指标,和LCP一样,TBT也会在 Lighthouse 6.0 以上被加入性能分数计算。
Cumulative Layout Shift
累积布局偏移(CLS)量化了
在页面加载期间,视口中有多少元素移动。
这里有个例子,移动内容(提示元素在页面顶部加载,并将页面内容向下移动)可能会让用户错过他们想要点击的按钮:
更糟糕的是,这种内容偏移可能会导致用户点到他们本不想点击的按钮。
这里有另一个例子,页面内容移动几个像素会引起相当大的麻烦:
一个演示布局稳定性对用户负面影响的截屏。来源:web.dev[14]
无论是以一种增加意外点击几率的方式加载广告,还是在加载新闻图片时文本向下移动,内容的意外移动都会让人非常不舒服。
Cumulative Layout Shift[15] 通过测量它对用户发生的频率,来帮助您解决这个问题。它引入了用户体验中一个全新的类别 —— 可预测性。
下面是一些常见的布局不稳定问题,以及它们的解决方法:
- 对于没有指定尺寸的图像,浏览器会先渲染一个 1x1 像素的占位直到整个图片下载完成,一旦图像渲染,它会导致布局的其余部分发生变化。为了避免这个问题,可以为 img 元素添加 width 和 height 属性[16]。
- 在渲染内容之后异步获取数据然后插入,可能会导致布局变化。这种情况下,一种比较好的实践是用内容占位符,这样真正内容加载后布局就不会产生太大的变化。
- 广告通常是异步加载的,在加载时可能会取代其他内容。如果这导致了意外点击,那就更令人心烦了,所以最好提前定义广告空间的尺寸。
- 利用某些 CSS 属性做动画可能会导致布局变化[17],在大部分情况下,你可以用
transform
属性做动画来避免它。 - 当利用
font-display: swap
来进行新旧字体切换时,由于字体之间的大小差异,当新字体加载并替换后备字体时,页面布局通常会发生变化。为避免此问题,你可以在 font-style-matche[18]r 调整不同字体的尺寸,以减小字体切换对布局的影响。
测量累积布局偏移
当渲染的元素在页面加载期间移动时,它们会被标记为不稳定,并且它们在相对于视口的移动决定了布局偏移分数。CLS 仍然在积极开发中,具体的公式可能会变,但目前来说,布局偏移的分数是由以下因素决定的:
- 不稳定元素移动的距离 —— 距离系数。
- 受不稳定元素影响的区域面积 —— 影响系数。
紫色箭头代表距离系数,蓝色矩形代表影响系数
在上面的例子中,元素向下移动了视口高度的 ⅓,所以距离系数是 0.33
。
元素在其起始位置和移动后的位置所占的面积占视口总面积的 ⅔,因此影响分数为 0.66
。
layout shift score = distance fraction * impact fraction
布局偏移得分为 0.33 × 0.66 = 0.2178
。
累积布局偏移分数,是所有不稳定元素在页面加载期间移动的分数之和。
CLS分数越低越好,因为这意味着
在页面加载过程中发生的内容的偏移较少。
如果上面例子中的元素只相对视口移动了 10%,那么距离系数就是 0.1
,影响系数是 0.4
。布局偏移得分就仅为 0.1 × 0.4 = 0.04
。
一个理想但不现实的情况是,CLS 得分为 0。在现实中的 Chrome 用户体验报告中,CLS 分数小于 5 即可被认为是理想的。不同类型的网站可能会有目的的移动内容[19]。此时的高 CLS 分数并不意味着糟糕的用户体验。任何情况下,监测 CLS 都有助于发现预期之外的布局偏移增长并且修复布局不稳定的问题[20]。
CLS 已经在 Chrome 用户体验报告中可用,你也可以在 JavaScript 中通过 Layout Instability API 测量 CLS[21]。Calibre 和 Lighthouse 也会加入这个能力。
通过跟踪现代 Web 性能指标来提升客户体验
选用你趁手的性能监控武器,自动化的性能监控是跟踪优化和捕获回归问题的关键。有了现代 Web 指标提供的思路,你可以不断的完善用户体验,并创造出伟大的产品。
参考资料
[1]
load time: https://developer.mozilla.org/en-US/docs/Web/Events/load
[2]
DOMContentLoaded: https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event
[3]
经过测试: https://calendar.perfplanet.com/2019/developing-the-largest-contentful-paint-metric/
[4]
阻塞渲染的资源: https://web.dev/render-blocking-resources/
[5]
最小化关键请求链: https://calibreapp.com/blog/critical-request
[6]
提取关键 CSS: https://web.dev/extract-critical-css/
[7]
字体加载策略: https://www.zachleat.com/web/comprehensive-webfonts
[8]
Calibre: https://calibreapp.com/
[9]
Largest Contentful Paint API: https://wicg.github.io/largest-contentful-paint/
[10]
性能得分计算器: https://paulirish.github.io/lh-scorecalc/
[11]
渲染网页: https://calibreapp.com/blog/investigate-animation-performance-with-devtools#demystifying-rendering
[12]
长任务: https://web.dev/long-tasks-devtools/#what-are-long-tasks
[13]
Time to Interactive (TTI): https://calibreapp.com/blog/time-to-interactive
[14]
web.dev: https://web.dev/cls/
[15]
Cumulative Layout Shift: https://calibreapp.com/blog/cumulative-layout-shift
[16]
可以为 img 元素添加 width 和 height 属性: https://www.youtube.com/watch?v=4-d_SoCHeWE&feature=youtu.be
[17]
利用某些 CSS 属性做动画可能会导致布局变化: https://calibreapp.com/blog/investigate-animation-performance-with-devtools#layers-and-compositing
[18]
font-style-matche: https://meowni.ca/font-style-matcher/
[19]
可能会有目的的移动内容: https://web.dev/cls/#vs.
[20]
修复布局不稳定的问题: https://dev.to/chromiumdev/fixing-layout-instability-176c
[21]
在 JavaScript 中通过 Layout Instability API 测量 CLS: https://dev.to/chromiumdev/measuring-cumulative-layout-shift-cls-in-webpagetest-5cle