前端性能指标浅析

4,942 阅读11分钟

说起性能,大家可能第一时间想到的就是用户从打开某个url到内容呈现在屏幕上,这个过程越快表示性能越好。但是性能除了渲染,还包括了可交互这块,并且前面提到快,是需要具体的数值来量化的。在性能量化这块,Chrome提出了一个RAIL衡量模型,RAIL模型的一种以用户为中心的性能模型,RAIL 代表 Web 应用生命周期的四个不同方面:响应、动画、空闲和加载。而本文要分析的部分指标,就是基于RAIL模型提出的。

Untitled.png

端性能指标主要可以分类两类,一类是网络性能,比如DNS解析时间、TCP握手时间,还有一类则是页面加载/渲染/可交互性能。本文只针对第二类的性能指标进行分析。

对于第二类的指标,Chrome指出核心指标有三个,分别是Largest Contentful Paint 最大内容绘制 (LCP)、First Input Delay 首次输入延迟 (FID) 和 Cumulative Layout Shift 累积布局偏移 (CLS)。还有一些非核心指标但也是重要指标,本文内容也会介绍。这些指标都主要是围绕以下4个关键问题构建。

是否正在发生? 导航是否成功启动?服务器有响应吗?


是否有用? 是否渲染了足够的内容让用户可以深入其中?


是否可用? 页面是否繁忙,用户是否可以与页面进行交互?


是否令人愉快? 交互是否流畅自然,没有延迟和卡顿?

三大核心指标的阈值

Untitled_1.png

说明

  • 此篇文章主要通过chrome开发者工具进行性能指标分析,主要用到了性能面板和lighthouse面板, 具体的使用教程可以查看官方文档
  • 为了增加测试场景,部分资源会加query参数sleep=x,表示这个资源加载需要x秒。
<link rel="stylesheet" href="./css/index.css?sleep=3000" />

FP (First paint)

红框处为FP时间,“FP”两个字刚好被同层的“时间”两个字挡住,目前只能通过鼠标active状态的popper看到。FP是首次绘制的意思,常常被用来衡量白屏时间。这个指标要注意的是首次绘制这个概念,一旦页面发生变化,也就是说和初始状态不同,哪怕是一个像素的变化,也算是首次绘制。

Untitled_2.png

FCP(First Contentful Paint)

FCP这个指标被Google列为重要指标,从上图可以看到,FCP排在DCL后面,其含义是

“首次内容绘制 (FCP) 指标测量页面从开始加载到页面内容的任何部分在屏幕上完成渲染的时间。对于该指标,"内容"指的是文本、图像(包括背景图像)、<svg>元素或非白色的<canvas>元素。”

Google建议这个值控制在1.8 秒以内。

在实际情况中,FP和FCP这两个指标通常是紧挨着的,但是大家可能对首次绘制和首次内容绘制两个相似的概念感到疑惑,所以我为了让大家能更清晰理解FP和FCP的区别,故意把这里的时间间隔拉大了。这里的时间间隔大概是3s

Untitled_3.png

面先看看html代码了解为什么间隔了3s

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      p {
        width: 100px;
        height: 100px;
        background-color: red;
      }
    </style>
  </head>
  <body>
    <p id="test"></p>
    <div></div>
    <link rel="stylesheet" href="./css/index.css?sleep=3000" />
    <div>123</div>
  </body>
</html>

首先html从上往下解析,解析完后css那个link还没下载完,所以会阻塞页面渲染,但是实际这时候页面会先渲染link标签之上的内容,也就是一个空的P标签,背景色是红色,所以这时候就是FC时间,因为要达到FCP的条件之一是要渲染文本,但这是仅仅是一个空标签。

接下来css的加载会阻塞dom渲染,等待3s后才渲染了本文节点,满足了到达FCP的条件。看到这里,相信大家对FP和FCP的不同点有了较为清晰的理解。下面还有一张图可以参考

Untitled_4.png

LCP(Largest Contentful Paint

FP和FCP的缺点不一定与用户在屏幕上看到的内容相对应,比如初始化的页面loading,那么这个值再小,也不能代表网页的性能。为了捕获到更多初始绘制后的加载体验,Google引入了LCP,其含义是

最大内容绘制 (LCP) 指标会根据页面首次开始加载的时间点来报告可视区域内可见的最大图像或者文本块完成渲染的相对时间。

如何确定一个元素的大小?

关于如何确定最大元素,这一块的逻辑比较复杂,因为不是仅仅判断元素的宽高尺寸,实际上还会综合考虑子元素等各种情况,比如有元素延伸到可视区域外则不会计入元素大小,说明不是单纯的考虑元素宽高。这一块建议大家使用实际项目的页面在开发者工具中进行分析。附上官方描述

报告给最大内容绘制的元素大小通常是用户在可视区域内可见的大小。如果有元素延伸到可视区域之外,或者任何元素被剪裁或包含不可见的溢出,则这些部分不计入元素大小。

对于在原始尺寸之上经过调整的图像元素,报告给指标的元素大小为可见尺寸或原始尺寸,以尺寸较小者为准。例如,远小于其原始尺寸的缩小图像将仅报告图像的显示尺寸,而拉伸或放大成更大尺寸的图像将仅报告图像的原始尺寸。

对于文本元素,指标仅考量其文本节点的大小(包含所有文本节点的最小矩形)。

对于所有元素,通过 CSS 设置的任何边距、填充或边框都不在考量范围内。

注意,最大元素随着页面渲染,是会变化的。还是从拿一张图说明,最大的元素一开始是文字,后面又变成了图片。

Untitled_5.png 如果在渲染过程中,判断了一个元素是最大元素,然后通过js又删除了这个元素,如果后面没有更大的元素符合条件,那么最大的元素还是被删除的元素。从下面这张图可以很清晰的看到,蓝色方块被删掉了,但还是最大元素。

Untitled_6.png 如果渲染过程中又插入了一个更大但元素,那么LCP也会改变。如下图所示,红色方块是后面新增的。值得注意的是,这个新增元素通过setTimeout异步插入也会更新LCP。


// setTimeout的形式 或者在script标签前加link下载css阻塞
<body>
    <div class="div1">div</div>
    <script>
      setTimeout(() => {
        const ele = document.createElement('div')
        ele.className = 'div2'
        ele.innerText = 'div2'
        document.body.appendChild(ele)
      }, 2000)
    </script>
  </body>

Untitled_7.png 但是一旦和页面发生交互,那么LCP将不再更新。如下图所示,新增了一个按钮,如果渲染红色方块前点击了按钮,那么LCP将停止更新。

Untitled_8.png

FID(First Input Delay)

前面三个指标都是渲染相关的,这个指标的作用量化网站对用户交互的响应度如何,建议控制在100ms以内,定义如下:

FID 测量从用户第一次与页面交互(例如当他们单击链接、点按按钮或使用由 JavaScript 驱动的自定义控件)直到浏览器对交互作出响应,并实际能够开始处理事件处理程序所经过的时间。

下面举个例子说明,如下的html,浏览器解析到script标签,便会停止解析直到js脚本下载并执行完。但是这段阻塞期,浏览器会先渲染一遍已经解析好的内容,所以页面会看到一个input框,当js下载完后,主线程一直在执行js(内容是一个耗时很久的for循环),这时候浏览器便会无法影响用户的输入操作。

<body>
    <div class="div1">div div</div>
    <input type="text" />
    <script src="./js/index.js?sleep=2000"></script>
    <div class="div2">div</div>
    <!-- <img src="./img/100px.png" alt="" /> -->
  </body>

等待js执行完的那一段时间,就是用户感受的FID值。

TTI(Time to Interactive)

TTI指标测量是能持续/流畅响应用户操作的时间,当页面已经进行首次绘制后(FCP),此时浏览器不一定是能响应用户操作的,比如上面FID举的例子,或者是同构应用,页面能很快渲染,但是在客户端还需要一个激活过程。

TTI 指标测量页面从开始加载到主要子资源完成渲染,并能够快速、可靠地响应用户输入所需的时间。

TTI的计算方式较为复杂,有以下四点

1.先进行First Contentful Paint 首次内容绘制 (FCP)。

2.沿时间轴正向搜索时长至少为 5 秒的安静窗口,其中,安静窗口的定义为:没有长任务且不超过两个正在处理的网络 GET 请求。

3.沿时间轴反向搜索安静窗口之前的最后一个长任务,如果没有找到长任务,则在 FCP 步骤停止执行。

4.TTI 是安静窗口之前最后一个长任务的结束时间(如果没有找到长任务,则与 FCP 值相同)。

注意和上面FID指标的对比,FID测量的是用户首次操作卡顿的时间,TTI测量是FCP后到浏览器能够流畅响应用户输入的时间,判断浏览器是否能够流畅影响用户则是根据上面的几点方式。

拿一张举例说明,白色方块则是安静窗口,因为满足5s内没有长任务和两个以上的get请求,找到安静窗口后,再往前搜索最后一个长任务,也就是红色虚线处,那么到这个橙色长任务的结束时间就是TTI时间

Untitled_9.png 如果没有长任务,TTI和FCP时间几乎是相同的。(截图来自chrome开发者面板的LIghthouse工具)

Untitled_10.png 下面看看实际有长任务的情况,html还是上面FID举例的那个,在FCP后,会用2s下载一个js文件,并且js文件是一个长任务,可以看到TTI和FCP之间差了大概2s的时间。

Untitled_11.png

TBT(Total Blocking Time)

TBT测量的是FCP到TTI这个区间段,每个长任务段阻塞时间之和。比如长任务的定义是超过50ms,那么阻塞时间是总执行时间-50ms。也就是说一个长任务总时间是60ms,那么阻塞时间实际上是60-50=10ms。

比如下面这两张图,第一张图是FCP到TTI之间各个任务的执行时间段,第二张棕色部分减去了50ms,所以棕色部分相加才是TBT时间。

Untitled_12.png

Untitled_13.png TBT指标可以理解为是TTI指标的一个补充指标,TTI描述的是页面什么时候可以持续响应交互,TBT则是描述阻塞的严重程度。举个详细例子说明

在FCP到TTI的10s内,都有长任务,但是分布不同。第一种情况是只有一个长任务,但这个长任务长达10s,那么TBT计算但结果是9950ms,但是如果前5s分布了一个长任务。执行只有55ms,后5s又分布了一个长任务,执行也只有55ms,那么TBT计算结果其实是10ms。但是注意以上两种情况,根据TTI的计算规则,他们的TTI时间都是一样的,因为都是10s后才有一个满足条件的窗口。这两种情况对于用户交互来说,体验也是非常巨大,TTI指标无法区分,但是TBT可以量化。

CLS(Cumulative Layout Shift)

CLS指标测量的是页面的视觉稳定性,较低的CLS有助于确保页面是令人愉悦的。

相信大家可能都遇到过这种情况,尤其是阅读图文网站的时候,当前正在阅读的文字被图片的渲染挤到下面去了,如果有那个图片加载,你可能要花很久才能找到之前正在阅读的问题。出现这种情况的一个最常见原因就是img标签没有固定宽高。

请注意,只有当现有元素的起始位置发生变更时才算作布局偏移。如果将新元素添加到 DOM 或是现有元素更改大小,则不算作布局偏移,前提是元素的变更不会导致其他可见元素的起始位置发生改变。

这个指标非常容易理解,所以不举实际例子,若想了解详细的计算方式,可以通过web.dev/cls/了解。