基于 Web Vitals 的前端性能优化实践
2023-10-17 · 12,846 chars · 65 min read
1. 性能指标#
在做性能优化之前,首先要明确相关指标。我们要带着明确的目标和方向去做优化,而不是随意的压压图片、加个预加载了事。
明确相关性能指标,也就是要明确:
- 衡量哪些性能指标?
- 为什么是这些指标?
- 这些指标该如何测量?
- 性能上,怎样算好,怎样算不好?
1.1 传统性能指标#
以前,前端有很多常用的性能指标,例如:
- FP(First Paint)
- TTFB(Time to First Byte)
- FCP(First Contentful Paint)
- SI(Speed Index)
- FMP(First Meaningful Paint)
- TTI(Time to Interactive)
- FCI(First CPU Idle)
Lighthouse V5 版本,主要测量的就是 FCP、SI、FMP、FCI、TTI 等几个指标。
这些「传统」的性能指标,和基于 Lighthouse 的性能测量,其实存在一些明显的问题。
比如,一些指标,虽然依然具有参考价值,但并不能很好的体现用户侧的真实体验。像 TTI,它的计算过程很复杂,要求有 5s 的静默窗口(无长任务 & 小于两个 GET 请求),然后沿着时间轴反向搜索 FCP 后的最后一个长任务,即为 TTI 时间。TTI 体现的是页面要多久才能具备完整的可交互性,但并不代表在此之前用户的交互都无法得到响应。当前一些比较复杂的前端应用,可能会分块按需加载,或者异步的延迟加载,都有可能把 TTI 时间拉得更长。所以,TTI 和 FCI 这类指标就显得有些过于「理论」了,脱离了实际的应用场景。
另外还有一点就是,以往测量前端性能,大多是使用 Chrome DevTools 自带的 Lighthouse,最多开一个「无痕窗口」来屏蔽浏览器扩展的影响。这样的测量方式,同样不能很好的反映用户的实际体验。在用户的真实使用场景里,网络环境、设备情况、甚至是用户个人账号情况,都可能极大的影响性能和体验。
1.2 Web Vitals#
所以基于此,2020 年 5 月,作为改善网络用户体验的持续举措的一部分,Google 推出了一套以用户为中心的标准化指标 —— Web Vitals。我们团队从 21 年底开始,一直都是基于 Web Vitals 来衡量和优化性能的。
1.2.1 概述#
Web Vitals 包含三个「核心指标」,也就是 Core Web Vitals(CWV):
其中,LCP 表示页面的「最大内容绘制」,用来衡量页面的加载性能。相比于其他指标,从用户的视角看,最大内容的绘制时间更能代表页面加载的速度。
FID 表示页面的「首次输入延迟」,用来衡量页面的交互性。例如当用户点击某个按钮时,程序要等多长时间才能响应。
CLS 表示页面的「累积布局偏移」,用来衡量页面的视觉稳定性,避免页面的闪烁给用户带来较差的体验。
Lighthouse 从 V6 版本开始,删除了一些旧指标,添加了 LCP、FID、CLS。目前最新版本的 Lighthouse V10,只包含 FCP、TBT、LCP、FID、CLS 五个指标。
上面图片里已经标识了 Google 官方对各个指标 good、need imporvement、poor 的定义。不过大家也可以根据团队的实际情况来定义基准线。
Note
说明:这里没有从概念上严格的区分 Web Vitals 和 Core Web Vitals。Core Web Vitals 仅包含 LCP 等三个指标,是 Web Vitals 的核心子集。而 Web Vitals 是个更广泛的概念,包含 TTFB、FCP、TBT 等更多的指标,这些指标很难在用户端实际测量,不是以用户为中心的指标,所以我们一般只作为辅助工具和补充。本文主要关注 Core Web Vitals。
1.2.2 优势、不足与发展#
新的 Core Web Vitals 一定程度上解决了传统指标的问题。
首先是更加系统化。相比于之前众多碎片化、理论化的指标,Web Vitals 提炼了三个核心指标。像加载性能,就全部收敛到了 LCP 上,避免了过往优化单一指标,数据效果明显,但体检提升不大的尴尬。
其次是完全「以用户为中心」 。比如新增的 CLS,这个概念在 Web Vitals 之前提到的比较少,但是相信大家都遇到过。设想,网络不好的时候,打开一个页面,我要点的按钮已经展示出来了,但当我点下去的时候,按钮上方的广告加载完成,将按钮挤了下去,直接点在了广告上......所以,视觉稳定性作为影响用户体验的关键,也纳入到了核心指标内。除了指标方面的转变,测量方式上同样也更倾向于使用基于真实用户的数据采集(这个在下一节聊)。
Web Vitals 并不完美,依然也存在一些不足。比如说 FID,它仅考虑第一次交互动作时的延迟,一些复杂的 Web APP,比较耗费性能的部分,往往不在页面刚启动的时候,而是贯穿整个用户使用周期的。近期 Chrome 团队提出了新的指标 —— INP (Interaction to Next Paint),相比于 FID 只测量首次交互的延迟,INP 关注用户与页面进行的所有交互的延迟。预计 24 年 3 月,INP 会取代 FID。
Web Vitals 就是目前最佳的性能度量指标,大家是很有必要仔细了解他们的定义、计算方式等,这里就不再进一步展开了。下面,咱们聊一下「性能检测工具」。
2. 检测工具#
这部分就简单介绍下。要想真正要熟悉它们,还是得实际用起来,在实践中积累。
「检测工具」根据数据源的不同,大致上了可以两类:
- 基于真实用户的数据
- web-vitals
- Chrome 用户体 验报告(CrUX)
- 基于实验室数据
- PageSpeed Insights
- Chrome DevTools
- Lighthouse
- Web Vitals Extension
这里面的 CrUX,是来自「数百万」个网站上的真实用户体验数据的「公共数据集」;PageSpeed Insights 是个在线的工具,结合了 Lighthouse 的实验室数据和来自 CrUX 的真实用户数据。这两个实际用的比较少。
总的来说,建议以 web-vitals 采集的真实数据为准,衡量页面性能。开发和优化过程中,使用 Chrome DevTools、Lighthouse、Web Vitals Extension 等查看页面的加载和运行情况,分析判断优化的方向和方法。
下面简单说下几个比较常用的。
2.1 web-vitals#
刚才也提到过了,页面实际的性能表现,还是应该以用户侧的真实采集的数据为准。
Google 的 Chrome 团队开源的同名库: web-vitals ,就是用来采集用户侧的性能数据的。
import {onCLS, onFID, onLCP} from 'web-vitals'; function sendToAnalytics(metric) { // Replace with whatever serialization method you prefer. // Note: JSON.stringify will likely include more data than you need. const body = JSON.stringify(metric); // Use `navigator.sendBeacon()` if available, falling back to `fetch()`. (navigator.sendBeacon && navigator.sendBeacon('/analytics', body)) || fetch('/analytics', {body, method: 'POST', keepalive: true}); } onCLS(sendToAnalytics); onFID(sendToAnalytics); onLCP(sendToAnalytics);
前端部分使用起来还是比较简单的,但是要配合一些服务端的数据存储、 查询和可视化工具才行。
这是我公司的 wapm 工具:
wapm 这类的前端基建,需要一定开发成本。如果团队没有类似的基建,也可以考虑使用一些开源的数据可视化工具,比如 Grafana
2.2 Chrome DevTools 和 Lighthouse#
Chrome 的 DevTools 应该每个前端开发用得最多的工具之一,它的 Performance 面板和 Lighthouse 面板都可以测量性能。
DevTools 是前端弄清楚「页面加载&运行情况」的重要手段,唯一要注意的就是屏蔽掉「浏览器扩展」的影响,打开隐身窗口或者新建一个空白账号都可以。
2.3 Web Vitals Extension#
接下来介绍几个浏览器扩展,用起来也很方便,大家可以安装上试试。
Chrome 扩展:Core Web Vitals Visualizer:UI 美观,功能完备,可以方便的查看页面的 LCP、CLS 等。
3. 系统级优化实践#
在了解「性能指标」和「检测工具」之后,就是真正的重头戏——「性能优化」 了。
根据实践过程中的总结,所有的优化手段,大致可以分为两类:
- 系统级优化:顾名思义,就是系统级的优化手段,通常不针对具体某个功能或指标。比如,前端的架构选型,项目的脚手架、模板、编译构建,组件库等等,还有服务端、客户端配合做的一些优化。
- 针对性优化:大多是针对某个具体的指标,某种具体类型的资源。手段较多,甚至矛盾(域名分散 vs 域名收敛),需要具体情况具体分析。比如优化资源的加载优先级、动态 import、切分长任务等等。
不过这个分类法不算特别严谨,这里主要为了结构更加清晰。接下就挑一些效果比较明显的措施,逐个聊聊,首先是系统级优化。