性能指标概述
说到网页性能,我们就像是在调教一辆赛车——你得让它跑得快,还要让乘客坐得舒服。为此,我们得盯紧几个关键指标:首次有意义渲染时间(FMP)、首次内容绘制时间(FCP)、可交互时间(TTI)、页面加载时间(PLT),以及最大内容绘制时间(LCP)。FCP 和 PLT 可以通过浏览器自带的工具轻松拿到,而 FMP 和 TTI 则需要我们施展点“黑魔法”,用算法逻辑来推测。
整个代码的风格是以ts来写的,使用rollup来打包的,大小在10kb 左右。
项目仓库链接:PerformanceMonitor
一、首次有意义渲染时间(First Meaningful Paint, FMP)
- 定义: FMP 就是用户眼前一亮的那一刻——当他们终于看到页面的主要内容(如文本、图片等)时,这个时间点就是 FMP。它是用户真正开始觉得“哇,我终于看到了有用的东西!”的时刻。
- 实现方法:
- 用 MutationObserver 监听 DOM 变化,记录页面各部分的渲染时间。
- 计算当前 DOM 树的得分,找到分数变化最大的一刻,那就是 FMP 的时间点。
- 衡量标准:
- 理想的 FMP 时间在 1-2 秒以内——这样用户才不会失去耐心。
- FMP 时间过长会让用户觉得页面“有点慢”,所以最好优化页面结构和资源加载。
private calculateDOMScore(): number {
let score = 0;
const stack: { node: Element, depth: number }[] = [{node: document.body, depth: 0}];
while (stack.length > 0) {
const {node, depth} = stack.pop()!;
if (!node) {
score += 5;
continue
}
;
const weight = this.getNodeWeight(node);
const {width, height, areaPercent} = this.getNodeMetrics(node);
// 根据公式计算节点的分数
const nodeScore = (width || 1) * (height || 1) * (weight || 1) * areaPercent;
score += nodeScore;
// 将子节点添加到栈中
const children = node.children;
for (let i = 0; i < children.length; i++) {
stack.push({node: children[i], depth: depth + 3});
}
}
return score;
}
二、首次内容绘制时间(First Contentful Paint, FCP)
- 定义: FCP 是页面加载时,用户最早看到的内容元素(比如文本、图片、SVG 等)绘制到屏幕上的时间。换句话说,这是用户感知“页面开始加载”的那一刻。
- 实现方法:
- 使用 Performance API 中的
performance.getEntriesByType('paint')获取 FCP 时间点。 - 浏览器自动记录
first-contentful-paint事件,直接从性能条目中提取即可。
- 使用 Performance API 中的
- 衡量标准:
- 理想的 FCP 时间应在 1.8 秒以内。
- 超过这个时间,用户可能会觉得“咦,这页面是不是有点慢?”
三、可交互时间(Time to Interactive, TTI)
- 定义: TTI 是页面从渲染完毕到用户可以开始点击、滑动并获得响应的时间点。通俗点说,这时候页面就不再“摆设”,可以“玩耍”了。
- 实现方法:
- 从起始点(FCP 或 FMP)开始,找到一个不小于 5 秒的“静默窗口期”(没有长任务且网络请求数不超过 2 个)。
- 找到静默窗口期后,再找到最近的长任务,长任务结束的时间就是 TTI。
- 如果没有找到长任务,就用起始点时间作为 TTI。
- 如果前两步得到的 TTI 比
DOMContentLoadedEventEnd还早,就用DOMContentLoadedEventEnd作为 TTI。
- 衡量标准:
- 理想的 TTI 时间应在 5 秒以内——用户等待时间太长可不是个好体验。
四、最大内容绘制时间(Largest Contentful Paint, LCP)
- 定义: LCP 是指页面加载过程中,最大内容元素(如大图或主块级文本)绘制到屏幕上的时间点。说白了,就是用户看到页面上“最显眼”的内容的那一刻。
- 实现方法:
- LCP 的计算方式非常直观。浏览器在加载页面时,会追踪所有可见元素(如图片、文本、视频等)的加载情况,并记录最大的那个元素的加载时间。
- 浏览器会考虑:图像元素的加载、文本元素的加载、iframe 内容的加载以及背景图像的加载。
- 衡量标准:
- 理想的 LCP 时间应在 2.5 秒以内。
- 超过 4 秒就显得有点“拖沓”,需要优化页面资源和结构。
private calculateTTI(): void {
this.observer?.disconnect();
// 使用 FMP 作为起始点,如果没有 FMP,则使用 FCP
const startTime = this.fmpTime || this.fcpTime;
if (!startTime) {
console.warn('没有FMP或者FCP,无法计算TTI');
return;
}
const quietWindow = this.findQuietWindow(startTime, QuietWindowDuration);
if (!quietWindow) {
console.log('没有静默窗口期');
this.ttiTime = startTime;
} else {
const lastLongTask = this.findLastLongTaskBefore(quietWindow.end);
console.log('静默窗口期:', lastLongTask)
// @ts-ignore
this.ttiTime = lastLongTask ? lastLongTask?.duration + lastLongTask?.startTime : (startTime);
}
//
/**
* timing.navigationStart :浏览器处理当前网页的启动时间
* domContentLoadedEventEnd:返回当前网页所有需要执行的脚本执行完成时的Unix毫秒时间戳
*/
const timing = window.performance.timing;
const domContentLoadedEventEnd = timing.domContentLoadedEventEnd - timing.navigationStart;
if (Number(this.ttiTime) < domContentLoadedEventEnd) {
this.ttiTime = domContentLoadedEventEnd;
}
if (!this.ttiTime) return console.warn('没有TTI');
this.callback({
id: new Date().getTime().toString(),
name: 'TTI',
value: this.ttiTime,
rating: getRating(this.ttiTime, TTIThresholds),
})
}
五、页面加载时间(Page Load Time, PLT)
- 定义: PLT 是从页面开始加载到完全加载完毕的时间。它包括所有资源的下载和渲染,反映整个页面加载的时间。就像赛车从起点到终点的总时间一样。
- 实现方法: 使用
window.performance.timing获取从navigationStart到loadEventEnd的时间差来计算 PLT。 - 衡量标准: 理想的 PLT 时间应在 2-3 秒以内——超过这个时间,用户可能会觉得“这车怎么开得这么慢”。
六、指标之间的关系
-
FCP(首次内容绘制):
- FCP 是页面加载中第一个重要的时间点,通常是所有指标中最早发生的。
- FCP 通常小于 FMP、LCP 和 TTI。
-
FMP(首次有意义的绘制):
- FMP 通常出现在 FCP 之后,标志着用户感知到的主要内容首次被绘制。
- 在复杂页面中,FMP 通常介于 FCP 和 LCP 之间。
-
LCP(最大内容绘制):
- LCP 通常在 FMP 之后,标志着页面的主要内容已经加载完成。
- LCP 通常大于 FMP,接近 TTI。
-
TTI(可交互时间):
- TTI 是所有指标中最晚的,标志着页面已经完全准备好供用户交互。
- TTI 通常是最大的,超过 FCP、FMP 和 LCP。
七、简单页面上的指标关系
在简单页面中,这些指标通常遵循更为线性和一致的顺序:
- FCP ≤ FMP ≤ LCP ≤ TTI
而且:
- FCP 通常是最小的,因为它标志着第一个内容的渲染。
- FMP 略大于 FCP,标志着有意义内容的首次绘制。
- LCP 略大于 FMP,标志着最大内容的绘制完成。
- TTI 通常是最大的,表明页面已经完全可交互。
希望这些性能指标能帮助你像调教赛车一样,让你的页面跑得又快又稳。