编辑:邵一帆、毛宇、赵远景
web 技术目前在各平台大规模被使用,天然的跨 iOS、Android、PC、Mac、Linux 等平台,结合响应式网页设计(Responsive Web design ) ,一次开发,可以快速的支持多平台,在身处各地的广大用户的各类设备中进行访问。这时候就会产生一个问题,用户的设备是多种多样的,设备的系统类型、浏览器版本、处理器性能也不尽相同,如何监测用户打开网页的速度、效率,从而进行有效分析,发现潜在的性能瓶颈,并做针对性的优化成了前端工作当中一个重要的课题
本文将跟大家分享一下如何对前端性能进行监控,并对抽象性的指标进行分析。首先跟大家介绍一下耳熟能详的 performance.timing
,通过这个 api 可以获取到页面加载关于网络方面的一些信息。
performance.timing 返回一个 PerformanceTiming 对象,里面的数据为只读的毫秒数,包含了浏览器开始请求服务器到页面被渲染各个节点的时间,经过计算可以得出页面的各类性能指标。
const timing = performance.timing;
在浏览器执行,会返回以下的数据
将这些数据按照时间线串联起来,可以完整的看到网页的各个时间节点:
再经过计算便可以得到一些需要页面性能指标:
白屏时间
const general = timing.domComplete - timing.navigationStart;
重定向耗时
const redirect = timing.fetchStart - timing.navigationStart;
dns 解析耗时
const dns = timing.domainLookupEnd - timing.domainLookupStart;
tcp 连接耗时
const connect_tcp = timing.connectEnd - timing.connectStart;
发起请求耗时
const request = timing.responseStart - timing.requestStart;
html 下载耗时
const response = timing.responseEnd - timing.responseStart;
头部资源下载耗时
const assets = timing.domComplete - timing.responseEnd;
如果对 performance.timing
数据进行采集、存储、分析可以绘制出可视化的图表,各类指标一目了然。
performance.timing
可以检测一定的网页性能的数据,但是这个 API 目前已经从 web 标准中移除,MDN 网站上是这样描述的:
已弃用: 不再推荐使用该特性。虽然一些浏览器仍然支持它,但也许已从相关的 web 标准中移除,也许正准备移除或出于兼容性而保留。请尽量不要使用该特性,并更新现有的代码。
并且performance.timing
只能检测跟网络相关数据,对于一些前端用户交互更为细致的考量鞭长莫及。
但是聪明的人类总会制作出更加精美、抽象、全面的轮子,给我们开发进行使用,我们可以使用 PerformanceObserver 来进行前端性能的监测,下面对这个 API 的使用做一些简单的介绍。
PerformanceObserver
方法被用来响应各类 PerformanceEntry
事件,这些事件包含各类性能指标耗时、时间线。
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
console.log(
`name: ${entry.name}, type: ${entry.entryType}, start: ${entry.startTime}, duration: ${entry.duration}`
);
});
});
observer.observe({ entryTypes: ["mark", "measure"] });
performance.mark("registered-observer");
浏览器会打印以下的数据:
name: registered-observer, type: mark, start: 4254.199999999255, duration: 0
name: routeChange, type: mark, start: 6358.5999999996275, duration: 0
name: beforeRender, type: mark, start: 6406, duration: 0
name: Next.js-route-change-to-render, type: measure, start: 6358.5999999996275, duration: 47.40000000037253
name: afterRender, type: mark, start: 6409.0999999996275, duration: 0
PerformanceEntry.entryTypes
PerformanceEntry.entryTypes
包含的值的解释:
属性名 | 属性的类型 | 属性的描述 |
---|---|---|
element | string | 汇报元素加载的耗时 |
navigation | URL | 页面的地址 |
resource | URL | 资源解析的地址 |
mark | string | 用来标记调用 performance.mark()的名字 |
measure | string | 用来标记调用 performance.measure()的名字 |
paint | string | 'first-paint' 或者 'first-contentful-paint' |
longtask | string | 汇报长任务实例 |
通过 PerformanceObserver
方法取到这些参数、对应的值,就可以对页面各维度性能指标数据进行抽象性的分析了,下面一一列举几个比较重要的指标,并对这些指标的含义、原理、如何采集做出简要的解释。
TTFB: Time to First Byte 首字节加载耗时
TTFB 是检验服务器响应的一个关键性指标,并记录了从浏览器发起请求到浏览器接收到首字节的时间,能直接反映出服务器响应速度。如下图所示,包括重定向耗时、缓存(如果有)、DNS 解析、TCP 连接耗时以及请求耗时。
汇总来说包括以下部分:
由于 performance.timing
惨遭废弃,相关的数据可以通过这个 API 进行获取。
如何采集 TTFB 数据
const observer = new PerformanceObserver((list) => {
const [pageNav] = list.getEntriesByType("navigation");
console.log(`TTFB: ${pageNav.responseStart}`);
});
observer.observe({ entryTypes: ["navigation"] });
浏览器会打印出如下数据:
TTFB: 10.200000001117587
TTFB 的值越小越好,小于 800 毫秒为优秀,大于 1800 毫秒为糟糕。
FCP: First Contentful Paint 首次内容绘制 DOM
FCP 表示页面从开始加载到页面中某些元素(比如:图片、文字等)在浏览器中渲染出来的耗时,通过示意图可以较为清晰的理解这个概念性指标
如何采集 FCP
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
const metricName = entry.name;
const time = Math.round(entry.startTime + entry.duration);
console.log(metricName, time);
}
});
observer.observe({ entryTypes: ["paint"] });
在网页中执行会有如下数据的打印
first-paint 1139
first-contentful-paint 1139
FCP 在 1.8s 以下为优秀,1.8s-3.0s 为一般,3.0s 以上为较差,这个指标通过直觉判断也能有直观的感受,1.8 秒以内页面页面展示出内容,可以被称为秒开了。在控制台中也可以看到 first-paint 这个数据,这个数据不如 FCP 具备很强参考性,所以这里不做更多分析。
LCP: Largest Contentful Paint 最大内容绘制
上面讲到 FCP,你也许会注意到,部分内容被渲染,还存在其他一些的内容没有被渲染,这对用户访问网页还是存在一定的困扰,所以页面加载完成也需要进行监测。
通过下面的图片可以直观的看到 FPC 跟 LCP 的区别
如何采集 LCP 数据
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log("LCP: ", entry.startTime);
}
});
observer.observe({ entryTypes: ["largest-contentful-paint"] });
控制台会有如下的打印:
LCP: 99.099
LCP 小于 2.5 秒为优秀,大于 4.0 秒为糟糕
FID: First Input Delay 首次输入延迟
用户对于访问打开速度的感觉可以通过 LCP 来衡量,用户看到页面之后,变会对页面进行一些操作,用户需要等待多久可以操作页面,这也是一个重要的数据,用户的时间很宝贵,我们耽误不起,用户操作了页面,但是没反应,这是一个非常令人难以接受的事情。
如何采集 FID 数据
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
const time = entry.processingStart - entry.startTime;
console.log(entry.entryType, time);
}
});
observer.observe({ entryTypes: ["first-input"] });
function onClick() {
console.log("click");
}
<button click="onClick(this)">button</button>
控制台会有如下打印:
first-input 4.2000000001862645
FID 的数值越小越好,最好在 100ms 以下,这样用户看到页面展示之后,再点下去的那一下,页面的 js 脚本就已经执行好,可以响应用户的操作。
CLS: Cumulative Layout Shift 累积布局偏移
当用户对页面进行操作的时候,页面维持一个稳定的状态,不抖动、不错位这是一个基本的要求,如果页面发生大范围的抖动,用户可能会错误的点击到其他地方,产生错误的操作,使用户的感受很糟糕。
布局偏移分数
布局偏移分数 = 影响分数 * 距离分数
距离分数定义
不稳定的元素相对于视口(viewport)在水平或者垂直方向上发生了位移,前后偏移的量相对于视口(viewport)的百分比。下图可以看到内容部分相对于视口(viewport)偏移了 25%,可以得到距离分数为 0.25。
影响分数定义
不稳定元素发生位移的前后两帧占据视口(viewport)的比例,在下面的图片中,我们可以看到内容开始占据一半的屏幕,发生位移后往下移动了 25%,这样前后加起来就是移动了 75%,可以得到影响分数为 0.75。
计算
0.75 * 0.25 = 0.1875
这个例子中的内容部分移动的距离太过于大,显然用户不能得到一个好的体验。CLS 显然越小越好,小于 0.1 算优,大与 0.25 为很差。
如何采集
通过 layout-shift 这个 entryTypes 可以对 CLS 进行一些采集
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];
if (
sessionValue &&
entry.startTime - lastSessionEntry.startTime < 1000 &&
entry.startTime - firstSessionEntry.startTime < 5000
) {
sessionValue += entry.value;
sessionEntries.push(entry);
} else {
sessionValue = entry.value;
sessionEntries = [entry];
}
if (sessionValue > clsValue) {
clsValue = sessionValue;
clsEntries = sessionEntries;
console.log("CLS:", clsValue, clsEntries);
}
}
}
}).observe({ type: "layout-shift", buffered: true });
setTimeout(() => {
document.querySelector("div").style.marginTop = "25vh";
}, 1000);
<div
style="
width: 100vw;
height: 50vh;
background-color: beige;
display: inline-flex;
justify-content: center;
align-items: center;
"
>
<img style="width: 150px" src="xxx.gif" />
</div>
关于 CLS 更多的信息可以参考 layout-instability[1]
本文详细的介绍了如何采用 performance
API 和 PerformanceObserver
API 进行前端性能监控,然后从服务器、首屏渲染、用户初次可交互等角度,将指标数据进行统计分析,得出页面性能优化方案。随着收钱吧业务复杂度的提升,目前公司在涉及到交易、对账、提现等前端项目上,对网页性能有着极高的要求。降低用户对页面响应延迟容忍度,让用户获得最佳体验感,对于前端开发人员来说至关重要。目前团队从各个角度切入、已做出针对性的优化,实践下来得到了不少有用的经验。下一期将会跟大家分享如何结合本文所介绍抽象性的指标,对网页的性能做针对性的优化。
陈川,来自基础业务开发部
A proposal for a Layout Instability specification: https://github.com/WICG/layout-instability