概览
谷歌提倡的七个用户体验指标(也可以认为是性能指标),每个指标分别根据以下几点讲解:
- 指标本身的作用、测量、推荐时间区间等
 - 如何指标进行优化,该内容会在文末统一讲解
 

FP & FCP
首次绘制(白屏)FP(First Paint):这个指标用于记录页面第一次绘制像素的时间。
首次内容绘制(首屏)FCP(First Contentful Paint):这个指标用于记录页面首次绘制文本、图片、非空白 Canvas 或 SVG 的时间。
这两个指标看起来大同小异,但是 FP 发生的时间一定小于等于 FCP。
DCL:documentContentLoad L:onloaded

FP 指的是绘制像素,比如说页面的背景色是灰色的,那么在显示灰色背景时就记录下了 FP 指标。
但是此时 DOM 内容还没开始绘制,可能需要文件下载、解析等过程,只有当 DOM 内容发生变化才会触发,比如说渲染出了一段文字,此时就会记录下 FCP 指标。
因此说我们可以把这两个指标认为是和白屏时间相关的指标,所以肯定是最快越好。
上图是官方推荐的时间区间,也就是说如果 FP 及 FCP 两指标在 2 秒内完成的话我们的页面就算体验优秀。
// 白屏时间 = 页面开始展示时间点 - 开始请求时间点const FP = performance.timing.domLoading - performance.timing.navigationStart
new Promise((resolve, reject) => {new PerformanceObserver((list) => {resolve(list);}).observe({ entryTypes: ["paint"] })}).then((list) => {list.getEntries().forEach((entry) => {console.log(`${entry.name}: ${entry.startTime}`);})}).catch((error) => {console.warn(error);})
LCP
最大内容绘制LCP(Largest Contentful Paint):用于记录视窗内最大的元素绘制的时间,该时间会随着页面渲染变化而变化,因为页面中的最大元素在渲染过程中可能会发生改变,另外该指标会在用户第一次交互后停止记录。
LCP 其实能比前两个指标更能体现一个页面的性能好坏程度,因为这个指标会持续更新。
举个例子:当页面出现骨架屏或者 Loading 动画时 FCP 其实已经被记录下来了,但是此时用户希望看到的内容其实并未呈现,我们更想知道的是页面主要的内容是何时呈现出来的。
此时 LCP 指标是能够帮助我们实现想要的需求的。
上图是官方推荐的时间区间,在 2.5 秒内表示体验优秀。
// 增加一个性能条目的观察者new PerformanceObserver((entryList, observer) => {const perfEntries = entryList.getEntries();const LCP = perfEntries[perfEntries.length - 1];const { element, renderTime, startTime, size, url, loadTime } = LCP;observer.disconnect(); // 不再观察了}).observe({ entryTypes: ["largest-contentful-paint"] }); // 观察页面中最大的元素
TTI
首次可交互时间,TTI(Time to Interactive)。
这个指标计算过程略微复杂,它需要满足以下几个条件
- 从 FCP 指标后开始计算
 - 持续 5 秒内无长任务(执行时间超过 50 ms)且无两个以上正在进行中的 GET 请求
 - 往前回溯至 5 秒前的最后一个长任务结束的时间
 

这里你可能会疑问为什么长任务需要定义为 50ms 以外?
Google 提出了一个 RAIL 模型:
对于用户交互(比如点击事件),推荐的响应时间是 100ms 以内。
那么为了达成这个目标,推荐在空闲时间里执行任务不超过 50ms(W3C 也有这样的标准规定),这样能在用户无感知的情况下响应用户的交互,否则就会造成延迟感。
这是一个很重要的用户体验指标,代表着页面何时真正进入可用的状态。毕竟光内容渲染的快也不够,还要能迅速响应用户的交互。想必大家应该体验过某些网站,虽然内容渲染出来了,但是响应交互很卡顿,只能过一会才能流畅交互的情况。

new PerformanceObserver((list) => {list.getEntries().forEach((entry) => {const { attribution, duration, entryType, name, startTime } = entry});}).observe({ entryTypes: ["longtask"] })
FID
首次输入延迟FID(First Input Delay):记录在 FCP 和 TTI 之间用户首次与页面交互时响应的延迟。
这个指标其实挺好理解,就是看用户交互事件触发到页面响应中间耗时多少,如果其中有长任务发生的话那么势必会造成响应时间变长。
在上文我们就讲过 Google 推荐响应用户交互在 100ms 以内

// 增加一个性能条目的观察者new PerformanceObserver((entryList, observer) => {const firstInput = entryList.getEntries()[0];if (firstInput) {const { target, processingStart, duration, processingEnd, name, startTime } = firstInput// 开始处理的时间 - 开始点击的时间,差值就是处理的延迟let inputDelay = processingStart - startTime;if (inputDelay > 0 || duration > 0) {console.log(inputDelay)}}observer.disconnect(); // 不再观察了}).observe({ type: "first-input", buffered: true }); // 第一次交互
TBT
阻塞总时间TBT(Total Blocking Time):记录在 FCP 到 TTI 之间所有长任务的阻塞时间总和。
假如说在 FCP 到 TTI 之间页面总共执行了以下长任务(执行时间大于 50ms)及短任务(执行时间低于 50ms)
那么每个长任务的阻塞时间就等于它所执行的总时间减去 50ms
所以对于上图的情况来说,TBT 总共等于 345ms。
这个指标的高低其实也影响了 TTI 的高低,或者说和长任务相关的几个指标都有关联性。
CLS
累计位移偏移CLS(Cumulative Layout Shift):记录了页面上非预期的位移波动。
大家想必遇到过这类情况:页面渲染过程中突然插入一张巨大的图片或者说点击了某个按钮突然动态插入了一块内容等等相当影响用户体验的网站。
这个指标就是为这种情况而生的,计算方式为:位移影响的面积  位移距离。
以上图为例,文本移动了 25% 的屏幕高度距离(位移距离),位移前后影响了 75% 的屏幕高度面积(位移影响的面积),那么 CLS 为 0.25  0.75 = 0.1875。
CLS 推荐值为低于 0.1,越低说明页面跳来跳去的情况就越少,用户体验越好。毕竟很少有人喜欢阅读或者交互过程中网页突然动态插入 DOM 的情况,比如说插入广告~
let clsValue = 0;let clsEntries = [];let sessionValue = 0;let sessionEntries = [];new PerformanceObserver((entryList) => {for (const entry of entryList.getEntries()) {// 只将不带有最近用户输入标志的布局偏移计算在内。if (!entry.hadRecentInput) {const firstSessionEntry = sessionEntries[0];const lastSessionEntry = sessionEntries[sessionEntries.length - 1];// 如果条目与上一条目的相隔时间小于 1 秒且与会话中第一个条目的相隔时间小于 5 秒,// 那么将条目包含在当前会话中。否则,开始一个新会话。if (sessionValue &&entry.startTime - lastSessionEntry.startTime < 1000 &&entry.startTime - firstSessionEntry.startTime < 5000) {sessionValue += entry.value;sessionEntries.push(entry);} else {sessionValue = entry.value;sessionEntries = [entry];}// 如果当前会话值大于当前 CLS 值,// 那么更新 CLS 及其相关条目。if (sessionValue > clsValue) {clsValue = sessionValue;clsEntries = sessionEntries;// 将更新值(及其条目)记录在控制台中。console.log('CLS:', clsValue, clsEntries)}}}}).observe({ entryTypes: ['layout-shift'], buffered: true })
三大核心指标
Google 在提出了网站用户体验的三大核心指标,分别为:
- LCP
- 代表了页面的速度指标
 - 虽然还存在其他的一些体现速度的指标,但是上文也说过 LCP 能体现的东西更多一些。
 - 一是指标实时更新,数据更精确。
 - 二是代表着页面最大元素的渲染时间,通常来说页面中最大元素的快速载入能让用户感觉性能还挺好
 
 - FID
- 代表了页面的交互体验指标
 - 毕竟没有一个用户希望触发交互以后页面的反馈很迟缓,交互响应的快会让用户觉得网页挺流畅。
 
 - CLS
- 代表了页面的稳定指标
 - 尤其在手机上这个指标更为重要。因为手机屏幕挺小,CLS 值一大的话会让用户觉得页面体验做的很差
 
 
如何查看指标
web-vitals
使用 web-vitals,具体操作查看 github 介绍。
npm i web-vitals -D
import { getLCP, getFID, getCLS, getFCP, getTTFB } from 'web-vitals';getCLS(console.log);getFID(console.log);getLCP(console.log);getFCP(console.log);getTTFB(console.log);
Chrome DevTool —— Performance

如何优化性能
资源优化
该项措施可以帮助我们优化 FP、FCP、LCP 指标。
- 压缩文件、使用 Tree-shaking 删除无用代码
 - 服务端配置 Gzip 进一步再压缩文件体积
 - 资源按需加载
 - 通过 Chrome DevTools 分析首屏不需要使用的 CSS 文件,以此来精简 CSS
 - 内联关键的 CSS 代码
 - 使用 CDN 加载资源及 dns-prefetch 预解析 DNS 的 IP 地址
 - 对资源使用 preconnect,以便预先进行 IP 解析、TCP 握手、TLS 握手
 - 缓存文件,对首屏数据做离线缓存
 图片优化,包括:用 CSS 代替蹄片、裁剪适配屏幕的图片大小、小图使用 base64 或者 PNG 格式、支持 WebP 就尽量使用 WebP、渐进式加载图片
网络优化
该项措施可以帮助我们优化 FP、FCP、LCP 指标。
这块内容大多可以让后端或者运维帮你去配置,升级至最新的网络协议通常能让你网站加载的更快。
比如说使用 HTTP2.0 协议、TLS 1.3 协议或者直接拥抱 QUIC 协议~优化耗时任务
该项措施可以帮助我们优化 TTI、FID、TBT 指标。
使用 Web Worker 将耗时任务丢到子线程中,这样能让主线程在不卡顿的情况下处理 JS 任务
- 调度任务 + 时间切片,这块技术在 React 16 中有使用到。简单来说就是给不同的任务分配优先级,然后将一段长任务切片,这样能尽量保证任务只在浏览器的空闲时间中执行而不卡顿主线程
 
不要动态插入内容
该项措施可以帮助我们优化 CLS 指标。
- 使用骨架屏给用户一个预期的内容框架,突兀的显示内容体验不会很好
 - 图片要设置长宽,而是使用占位图给用户一个图片位置的预期
 - 不要在现有的内容中间插入内容,起码给出一个预留位置
参考资料
《还在看那些老掉牙的性能优化文章么?这些最新性能指标了解下》
《Using the Paint Timing API》
《Cumulative Layout Shift》 
