# 前言
# 用户角度的性能指标
# 用户感知的指标
- 感知的加载速度:网页加载并将其所有视觉元素呈现到屏幕的速度
- 加载响应速度:网页加载和执行任何必需的 JavaScript 代码的速度,以便让组件快速响应用户互动
- 运行时响应速度:网页加载后,网页能够以多快的速度响应用户互动
- 视觉稳定性:网页上的元素是否会以用户意想不到且可能会干扰互动的方式发生变化,比如图片加载后出现偏移闪动
- 流畅性:过渡和动画否以一致的帧速率渲染并流畅呈现
著名 2-5-8 原则:
- 当用户能够在 2 秒以内得到响应时,会感觉系统的响应很快;
- 当用户在 2-5 秒之间得到响应时,会感觉系统的响应速度还可以;
- 当用户在 5-8 秒以内得到响应时,会感觉系统的响应速度很慢,但是还可以接受;
- 而当用户在超过 8 秒后仍然无法得到响应时,会感觉系统糟透了,或者认为系统已经失去响应,而选择离开这个 Web 站点,或者发起第二次请求。
# 以用户体验为中心的性能指标(Google)
Google,于 2020 年提出了以用户为中心的 “核心网页指标”,衡量一个网页的用户体验(加载、互动、视觉稳定)优良度。具体的核心指标如下:
# FCP(First Contentful Paint)首次内容绘制
# 什么是 FCP?
FCP
,首次内容绘制。该指标用于测量从网页加载到任何一部分呈现在屏幕上的时间。用户访问 Web 页面过程中,在 FCP 之前页面处于白屏,没有实际内容。(如下图所示)
内容:是指文本、图片(包括背景图片)、
<svg>
元素或非白色<canvas>
元素。
# 什么算良好的 FCP 得分?
为了达到良好的用户体验,网页应尽力将 FCP 控制在 1.8 秒以内,不妨将网页加载的第 75 百分位作为衡量阈值。
- 小于
1.8
秒,表示页面加载较快,白屏时间短 1.8
秒至3
秒,表示页面加载较慢,但基本能接受3
秒以上,表示页面加载很慢,白屏时间过长
# LCP(Largest Contentful Paint)最大内容绘制
# 什么是 LCP?
LCP
,最大内容绘制。该指标用来测量从网页加载到网页最大内容(文本块 / 图片元素)呈现在屏幕上的时间。
最大内容元素有哪些?
<img>
元素<svg>
内的<image>
元素- 包含海报图片的
<video>
元素- 为自动播放
<video>
元素而绘制的第一帧- 动画图片(如
GIF
动画)的第一帧- 包含文本节点或其他内嵌级别文本元素的子项的块级元素
# 什么算良好的 LCP 得分?
为了达到良好的用户体验,网页应尽力将 LCP 控制在 2.5 秒以内,不妨将网页加载的第 75 百分位作为衡量阈值。
# CLS(Cumulative Layout Shift)
# 什么是 CLS?
CLS
,页面整个生命周期内发生的所有意外布局偏移的累计分数。(累计布局偏移分数 = 影响比例 * 距离比例)
影响比例
影响比例,是上一帧的所有不稳定元素与当前帧的可见区域(占总面积的比例)的并集就是当前帧的影响比例。 在上图中,一个元素占据了一帧的一半视口。然后,在下一帧中,该元素会向下移动 25% 的视口高度。红色虚线矩形表示元素在两个帧中可见区域的并集,在本例中占总视口的 75%,因此其影响比例为 0.75
。
距离比例
距离比例,是任何不稳定元素在框架内移动的距离(水平 / 垂直方向)的最大距离除以视口的尺寸(宽度 / 高度,以较大者为准)。
在上例中,最大的视口尺寸为高度,不稳定的元素移动了视口高度的 25%,使得距离比例为 0.25。
因此布局偏移分数为 0.75 * 0.25 = 0.1875
。
# 什么算良好的 CLS 得分?
# FID(First Input Delay)
# 什么是 FID?
FID
,测量的是用户首次与网页互动(点链接、点按钮等)到浏览器响应该互动的时间。
# 什么算良好的 FID 得分?
# INP(Interaction to Next Paint)
# 什么是 INP?
2024 年 3 月, INP
将取代 FID
,用于观察用户与网页进行的所有互动的延迟时间。INP 较低时意味着网页能够快速响应绝大多数用户互动。
# 什么算良好的 INP 得分?
# TTFB 首字节时间
# 什么是 TTFB?
TTFB
,用于测量资源请求与响应第一个字节之间的时间。
# 什么算良好的 TTFB?
# TTI(Time To Interacive)可交互时间
# 什么是 TTI?
TTI
,用于测量从网页开始加载到主要子资源已加载且能够快速响应用户输入的时间。(TTI,即是静默期之前的最后一个长任务结束时间。如果未找到长任务,则与 FCP 相同)
# TBT(Total Blocking Time)总阻塞时间
TBT
,用于测量 FCP 和 TTI 之间的总阻塞时间( TBT=FCP+TTI
)。这段时间内主线程处于阻塞状态的时间足够长,足以阻止输入响应。
例如,我们来看下图,展示了网页加载期间的浏览器主线程: 上面的时间轴包含五个任务,其中三个任务是长时间任务,因为其时长超过 50 毫秒。下图显示了每个耗时较长的任务的阻塞时间: 根据上图阻塞的任务时长,可计算得出 TBT = 200+40+105 = 345毫秒
。
更多指标详见:以用户为中心的效果指标
# 性能指标的计算
# 方式 1:引入 web-vitals 官方库进行计算
import { onFCP, onLCP, onFID, TTFB } from 'web-vitals'; | |
onFCP(console.log); | |
onLCP(console.log); | |
onFID(console.log); | |
onTTFB(console.log); |
# 方式 2:通过 performance api 进行计算
通过 performance api
,不仅可以计算网络相关耗时,也可以计算性能指标,比如白屏时间 FP、FCP、LCP、FID、CLS、TTFB 等。
# performance.timing
参数 | 作用 |
---|---|
navigationStart | (该页面的起始时间)同一个浏览器上下文的上一个文档卸载结束的时间戳;如果没有上一个文档,该值和 fetchStart 相同 |
unloadStart | unload 事件触发时的时间戳,如果没有上一个文档,这个值为 0 |
unloadEnd | unload 事件处理完成的时间戳,如果没有上一个文档,这个值为 0 |
redirectStart | 第一个 HTTP 重定向开始时的时间戳,如果没有重定向或重定向中一个不同源,则返回 0 |
redirectEnd | 最后一个 HTTP 重定向完成时的时间戳,如果没有重定向或重定向中一个不同源,则返回 0 |
fetchStart | 浏览器准备好使用 HTTP 请求来获取文档的时间戳。这个时间点会在检查任何应用缓存之前 |
domainLookupStart | 域名查询开始的时间戳,如果使用了持久连接或缓存,则与 fetchStart 一致 |
domainLookupEnd | 域名查询结束的时间戳,如果使用了持续连接或者缓存,则与 fetchStart 一致 |
connectStart | HTTP 请求开始向服务器发送时的时间戳,如果使用了持续连接,则与 fetchStart 一致 |
connectEnd | 浏览器与服务器之间连接建立(TCP 握手全部结束)的时间戳,如果使用了持续连接,则与 fetchStart 一致 |
requestStart | 浏览器向服务器发出 HTTP 请求时的时间戳 |
responseStart | 浏览器从服务器收到(或本地缓存读取)第一个字节时的时间戳 |
responseEnd | 浏览器从服务器收到(或本地缓存读取)最后一个字节时的时间戳 |
domLoading | 当网页 DOM 结构开始解析时的时间戳 |
domInteractive | 当前网页 DOM 结构解析完成,开始加载内嵌资源时的时间戳 |
domComplete | 当前文档解析完成的时间戳 |
loadEventStart | load 事件被发送时的时间戳,如果这个事件还未被发送,它的值将会是 0 |
loadEventEnd | load 事件结束的时间戳,如果这个事件还未被发送,它的值将会是 0 |
# 网络相关数据
# 1. 重定向耗时
redirectEnd - redirectStart |
# 2. DNS 解析耗时
domainLookupEnd - domainLookupStart |
# 3. TCP 链接耗时
connectEnd - connectStart |
# 4. HTTP 请求耗时
responseEnd - requestStart |
# 5. 解析 DOM 树耗时
domComplete - domLoading |
# 性能指标数据
Performance.timing 被弃用后,可通过 PerformanceObserver 来获取性能指标,具体计算如下:
# 1. 白屏时间 FP
白屏时间 FP(First Paint)指的是从用户输入 url 开始到页面有内容展现出来的时间节点。(这个过程包括 DNS 查询、TCP 连接、HTTP 请求、返回 HTML、解析 HTML)
FP
通过 PerformanceObserver
实例对象中监听名为 first-paint
的 paint
条目获取,具体代码如下:
const entryHandler = (list) => { | |
for(const entry of list.getEntries()){ | |
if(entry.name === 'first-paint') { | |
observer.disconnect() | |
} | |
// 白屏时间 | |
let FP = entry.startTime; | |
} | |
} | |
const observer = new PerformanceObserver(entryHandler) | |
observer.observe({type: 'paint', buffered: true}) |
# 2. 首次内容绘制 FCP
FCP
通过 PerformanceObserver
实例对象中监听名 first-contentful-paint
的 paint
条目获取,具体代码如下:
const entryHandler = (list) => { | |
for(const entry of list.getEntries()){ | |
if(entry.name === 'first-contentful-paint') { | |
observer.disconnect() | |
} | |
// 首次内容绘制时间 | |
let FCP = entry.startTime; | |
} | |
} | |
const observer = new PerformanceObserver(entryHandler) | |
observer.observe({type: 'paint', buffered: true}) |
# 3. 最大内容绘制 LCP
LCP
通过 PerformanceObserver
实例对象的 largest-contentful-paint
条目获取,具体代码如下:
const entryHandler = (list) => { | |
for(const entry of list.getEntries()){ | |
observer.disconnect() | |
// 最大内容绘制 | |
let LCP = entry.startTime | |
} | |
} | |
const observer = new PerformanceObserver(entryHandler); | |
observer.observe({ type: 'largest-contentful-paint', buffered: true }); |
# 4. 累计布局偏移分数 CLS
CLS
通过 PerformanceObserver
实例对象的 layout-shift
条目获取,具体代码如下:
const entryHandler = (list) => { | |
for(const entry of list.getEntries()){ | |
console.log('Layout Shift:', entry) | |
} | |
} | |
new PerformanceObserver(entryHandler); | |
observer.observe({ type: 'layout-shift', buffered: true }); |
# 性能检测工具
# LightHouse
Chrome 开发者工具 LightHouse 面板,主要针对 “Performance 性能、 Accessibility 可访问性、Best Practice 最佳实践、SEO 搜索引擎优化、Pregressive Web App 离线应用” 5 个方面做了分析。我们常使用该工具做页面 Performance 性能检测,并制定合适优化方案落地。
Performance 性能区域列出了 FCP、LCP、TBT、CLS、Speed Index 5 个核心指标,并给出具体优化方案。
# WebPage Test
WebPage Test,输入需要检测的网页 url 地址即可进行性能检测。
# 问题分析定位工具
# Chrome 开发者工具
# 1. Network 面板(定位网络请求问题)
Network 面板主要是用来分析定位网络请求问题,其面板主要有 “过滤、概览、查看详情”3 个功能区域。
# Network Filter 过滤
# Network Overview 概览
Network 面板可查看的基本信息有:
- 请求的 name 名称、size 大小、method 方法、status 状态码、scheme 协议类型、protocol 协议版本、type 类型、initiator 调用堆栈等
- 请求的响应时长
- 请求的瀑布图 waterfall
- Queued at 浏览器将资源放入队列时间
- Started at 浏览器开始处理资源的时间
- Queueing 资源放入队列的等待处理时间(如果遇到高优先级或请求超过并发限制,需要等待)
- Stalled 因放入队列而发生的停滞时间
- Request sent 请求发送的时间
- Waiting for server response 等待服务器端返回数据的时间(TTFB)
- Content Download 浏览器下载资源的时间
# Network 请求详情
# 2. Performance 面板(定位页面渲染性能问题)
Perfomance 面板,主要分析定位页面渲染性能问题(HTML 解析、JS 执行、CSS 解析布局计算重排、Painting 绘制)。我们来看看该面板几个区域的主要功能:
- 区域 1:控制面板:录制页面加载 / 操作、清空、快照 / 内存捕捉等
- 区域 2:概览面板
- FPS:帧率,每秒帧数
- CPU:处理各个任务花费的时间
- NET:各个请求花费时间
- HEAP:内存消耗记录,包括存在内存未销毁的 js 变量、文档量、dom 节点、事件监听、GPU 占用内存等
- 区域 3:线程面板
- Network:各个请求发生的时间点
- Frames:帧线程
- Main:主线程,负责加载、执行 JavaScript,解析 html、css,生成 render 树,完成绘制 painting(帮助我们理解浏览器的页面渲染原理)
- Raster:Raster 线程,负责完成某个 layer 或者某些块的绘制
- GPU:图层处理,负责将合成帧绘制到屏幕
- 区域 5:统计面板
# 3. Memory 面板(定位内存问题)
Memory 面板,主要用来分析定位内存问题,常用来辅助解决内存泄漏问题。我们来看看该面板记录内存的哪些信息:
- Constructor 数据类型结构,比如对象、数组、字符串、闭包等
- Distance 使用节点最短路径时距根节点 root 的距离
- Shallow Size 对象自身占用内存
- Retained Size 能释放的内存大小(保留大小)
# 4. Recoder 面板(页面操作问题)
Recoder 面板,主要可以记录页面交互过程,可用来做用户操作回放,排查页面操作相关问题。
# 5. 任务管理器(页面占用内存问题)
Chrome 任务管理器能够显示每个页面占用内存空间以及进程信息,可以辅助排查页面会话打开内存占用问题。
# 资源打包分析工具(webpack)
# 1. speed-measure-webpack-plugin 打包测速分析
使用 speed-measure-webpack-plugin 分析打包时每个 loader 和 plugin 执行耗时情况,用来打包测速分析。
# 2. webpack-bundle-analyse 包体积分析
使用 webpack-bundle-analyzer 分析打包后生成 Bundle 的每个模块体积大小。