Skip to content

You can't manage what you can't measure. —— Peter Drucker

「如果你无法衡量它,那么你就无法管理它」,性能优化同样如此,那么我们可以通过哪些指标来对页面性能、用户体验就行度量呢?

  • Performance API
  • 页面流畅度
    • FPS
    • RAF(requestAnimationFrame)
  • 首屏性能
    • FP、FCP、FMP
    • CWV(Core Web Vitals)

Performance API

通常我们将浏览器提供的可以进行测算和采集的 API 统称为 Performance API,该类型的对象可以通过调用只读属性 window.performance 来获得。

在日常的工作中,我们为了计算一个任务的耗时,通常会在任务执行前后利用 Date.now() 分别创建两个时间,最后取两者的差值作为任务执行耗时。但是 Date.now() 存在两个问题:

  1. 返回的时间戳是从 1970 年 1 月 1 日 00:00:00 UTC 开始经过的毫秒数,依赖于系统时间
  2. 精度仅到毫秒(ms)

为了对性能做精准的计算,我们可以选择 Performance API 提供的 performance.now() 来进行高精度计算,其特性为:

  1. 时间戳基于页面打开的时间计算
  2. 精度精确到微秒(us)

可以在控制台打印,更直观看出二者差异:

js
console.log(Date.now());        // 1655740456043
console.log(performance.now()); // 1070838.1999999285

页面流畅度

FPS

FPS (Frames Per Second, 帧率,每秒传输帧数 ),一般对于网页而言,最优的帧率在 60FPS,如果越接近这个值,页面就越流畅,帧率如果远低于这个值,用户可能会明显感觉到卡顿。

60FPS 意味着页面每隔 16.5ms (1/60) 就需要渲染一次,否则就会出现丢帧的现象,而浏览器中的 JavaScript 执行和页面渲染都是会相互阻塞的,如果在代码中有非常复杂的逻辑占用了大量的执行时长,就会导致页面出现卡顿。

在 Chrome 的 devtools 中我们可以执行 Cmd+Shift+P 输入 show fps 来快速打开 fps 面板,如下图所示:

Run Command

通过观察 FPS 面板,我们可以很方便的对当前页面的流畅度进行监控:

FPS Panel

我们在代码中如果想对当前页面的 FPS 帧率进行监控,可以参考如下这段示例代码:

js
let frame = 0;
let lastTime = performance.now();
let lastFameTime = performance.now();

const loop = function(time) {
    const now = performance.now();
    const fs = (now - lastFameTime);
    lastFameTime = now;
    let fps = Math.round(1000/fs);
    frame++;
    if(now > 1000 + lastTime) {
        fps = Math.round((frame * 1000) / (now - lastTime));
        frame = 0;
        lastTime = now;
    };
    window.requestAnimationFrame(loop);
}

requestAnimationFrame:

window.requestAnimationFrame() 告诉浏览器你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。

这里借用 MDN 的描述,顾名思义就是传入一个函数,让浏览器在下一次渲染之前进行调用。那么基于这个特性,结合上面提供的 FPS 计算示例代码,我们可以发现,如果我们持续对 requestAnimationFrame 进行调用,那么每次调用的间隔应该在 16.7ms 左右,即满足我们对于页面流畅度 60FPS 的要求,可以使用如下代码在控制台执行试试看:

js
let lastTime = 0;
const measure = () => {
    console.log(`${Date.now() - lastTime}ms`);
    lastTime = Date.now();
    requestAnimationFrame(measure);
};
measure();

首屏性能

首屏性能作为我们最关心的核心指标之一,在性能优化的场景中占据了相当大的比重,那么对于首屏的性能我们有哪些衡量指标呢?针对这个问题,Google 曾经提出过一系列的以用户体验为中心的性能指标。

FP

FP (First Paint, 首次绘制),代表浏览器第一次向屏幕传输像素的时间,仅表示当前已经开始绘制了,实际意义比较小。

FCP

FCP (First Contentful Paint, 首次内容绘制),代表浏览器第一次向屏幕绘制「内容」(只有首次绘制文本、图片 - 包含背景图、非白色)。

相比 FP,FCP 指的是浏览器首次绘制来自 DOM 的内容。例如:文本,图片,SVG,canvas元素等,这个时间点叫 FCP。

FMP

FMP (First Meaningful Paint, 首次有效绘制),表示页面中有意义的内容开始出现在屏幕上的时间点。它也是我们来衡量用户加载体验的主要指标

FMP 本质上是一个主观认知指标,是通过一个算法来猜测某个时间点可能是 FMP,但是计算方式过于复杂而且不准确,后来 Google 也放弃了 FMP 的探测算法,转而采用更加明确的客观指标 - LCP

CWV

CWV (Core Web Vitals, 核心 Web 指标),是适用于所有网页的 Web 指标子集,每位网站所有者都应该测量这些指标,并且这些指标还将显示在所有 Google 工具中。每项核心 Web 指标代表用户体验的一个不同方面,能够进行实际测量,并且反映出以用户为中心的关键结果的真实体验。

目前的 Web 核心指标由三个方面构成:页面加载性能交互性视觉稳定性,包含如下三个指标及阈值:

核心 Web 指标

  • LCP: Largest Contentful Paint,最大内容绘制,测量加载性能。为了提供良好的用户体验,LCP 应在页面首次开始加载后的 2.5 秒内发生。
  • FID: First Input Delay,首次输入延迟,测量交互性。为了提供良好的用户体验,页面的 FID 应为 100 毫秒或更短。
  • CLS: Cumulative Layout Shift,累积布局偏移,测量视觉稳定性。为了提供良好的用户体验,页面的 CLS 应保持在 0.1. 或更少。

LCP

LCP 关注的是首屏中最大元素渲染渲染的时间,和 FCP 不同的是,FCP 更关注浏览器什么时候开始绘制内容,比如一个 loading 页面或者骨架屏,并没有实际价值,所以 LCP 相较于 FCP 更适合作为首屏指标。

拿 Detail 页举例,在 FCP 时,商品图片并未加载,此时对于用户而言,一个近乎白屏的页面是不具备可交互价值的,在 LCP 时,图片已经完成了加载,首屏主要元素也几乎加载完毕,此时的时间作为首屏时间,才是比较接近用户体感的。

既然 LCP 是根据页面上占据面积最大的元素渲染时间确定的,那么元素包含哪些呢:

  • 图片
  • 内嵌在 svg 中的 image 元素
  • 视频的封面
  • 通过 url() 加载的 Background Image
  • 文字

webpagetest.org 上可以很直观的看到当前 LCP 元素的详情信息:

元素面积的计算规则有如下几点:

  • 在 viewport 内可见元素的大小,如果是超出可视区域或者被裁减、遮挡等,都不算入该元素大小
  • 对于图片元素来说,大小是取图片实际大小和原始大小的较小值,即 Min(实际大小,原始大小)
  • 对于文字元素,只取能够覆盖文字的最小矩形面积
  • 对所有元素,marginpaddingborder 等都不算

FID

FID 指标是指用户首次和网站进行交互到浏览器响应该事件的实际延时时间,可以想象一下,如果在你点击了一个 Button 后,页面没有任何变化,2-3s 后才开始响应,可想而知体验是非常糟糕的。

FID 判定的交互行为有:

  • 点击、触摸、按键等(不包含滚动和缩放)
  • 有事件绑定的行为,比如注册在某个 DOM 上的 click 事件

那么为什么会产生交互延迟呢?比如我在 button 上注册了一个 click 事件,例如:

js
btn.addEventListener('click', () => {
    // do something
});

按照预期,用户点击按钮的时候,回调函数会被直接触发,但是如果当前主线程被渲染、Long Tasks 占用,这个回调的执行就会被延后,就会导致 FID 时长增加。

但是 FID 作为一个「非客观值」,需要用户进行交互才能采集到,用户的交互时机,同样也会对指标的采集、统计造成影响。

CLS

CLS 是用来衡量视觉界面稳定性的一个指标,指的是页面产生的连续累计布局偏移分数。我们在日常业务中经常会用到懒加载、骨架屏等方式,用较低的成本先展示页面框架,再用动态渲染的方式,来对页面内容进行填充,如果此时布局发生变化,比如动态加载的元素和原本占位的元素大小不一致,可能就会导致用户误操作,影响用户体验,CLS 就是为了度量这类问题而存在。

当我们在说布局偏移的时候,指的是:页面中一个可见元素的起始位置发生改变,而元素的增删则并不会触发布局偏移。那么如何定义偏移的连续累计呢?有如下几个要素:

  • CLS 计算的并非页面整个周期的偏移分数之和,而是累计值最高的连续布局偏移
  • 偏移相隔的时间少于 1s,且整个窗口的最大持续时间为 5s,则被计为连续偏移