1. 简介
前端性能跟用户体验息息相关。举个栗子,当你打开乘车码扫码进站,网页白屏了很久才加载出来,延误了乘车时间;当你在微信抢红包时,点击按钮后延迟了一会才开始转圈圈,最终没抢到红包。当出现这样的情况,用户体验就不好了。有研究表明,网页加载时间超过3秒,有一半的用户会失去耐心而离开。由此可见,前端性能对用户体验很重要。
那么,什么是前端性能呢?
谈到性能,我们会想到高并发、高吞吐量,但这些是用户无法感知到的。对于用户来说,页面秒开、交互顺畅,才会觉得这个网站快。所以,前端性能关注的是页面加载速度和交互响应速度。
2. 页面加载
如果页面加载太慢,用户就会失去耐心而离开。那么,浏览器是怎么加载页面的呢?
- 首先,加载HTML并构建DOM。同时,同步地加载脚本(<script>),异步地加载异步脚本(<script async>) 和图片等资源。
- 然后,DOM构建完成之后,加载延迟脚本(<script defer> 和 <script type=‘module’>)。在以上过程中,非异步脚本都会等待CSS加载。
- 最后,DOM构建完成,所有脚本、CSS和图片等资源加载完成,即页面加载完成。
下图是页面加载的整个生命周期:
关键时间点如下:
- domInteractive:表示DOM构建完成的时间点(此时JavaScript已经可以访问DOM)。
- domContentLoadedEventStart:表示domContentLoaded事件处理器开始的时间点。(此时,脚本和CSS已经加载完成,异步脚本除外)
- domComplete:表示页面加载完成的时间点(包括DOM解析完成,所有脚本、CSS和图片等资源加载完成)。
这些关键时间点,客观地衡量了页面加载速度。但是对用户来说,能直接感知的是白屏时间。
对应的指标如下:
- FCP: First Contentful Paint即首次渲染出内容的时间点。
- LCP:Largest Contenful Paint即渲染出主要内容的时间点。与FCP相比,用户对LCP的感知会更明显。
3. 交互响应
用户看到页面渲染出来了,就会下意识地去操作页面,这时如果没有及时反馈,用户就会感觉到页面有延迟。
那么,为什么页面没有及时响应呢?
用户输入或点击按钮触发事件,网页会执行事件处理函数。如果处理时间过长,就会阻塞主线程,无法及时处理UI事件,造成页面卡顿。
对应的指标如下:
- TTI: Time To Interactive,即页面可交互的时间点。
- FID:First Input Delay,即用户首次输入的延迟时间。
- long tasks:如果一个任务执行时间超过50ms,就是long tasks。long tasks会阻塞主线程,造成页面卡顿。
- input delay:从用户输入到页面响应的时间差值。
4. 采集性能数据
网页的加载和渲染是发生在浏览器端的,所以依赖于浏览器提供的API来获取性能数据。
4.1. 采集页面加载性能
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
console.log(entry)
}
}).observe({type: "navigation", buffered: true});
4.2. 采集FCP&LCP
// 获取FCP
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
console.log(entry)
}
}).observe({type: "paint", buffered: true});
// 获取LCP
// 浏览器会多次报告 LCP ,而一般真正的 LCP 是用户交互前最近一次报告的 LCP
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
console.log('LCP candidate:', entry.startTime, entry);
}
}).observe({type: 'largest-contentful-paint', buffered: true});
4.3. 采集TTI&FID
浏览器没有提供直接获取TTI的API,但有方法可以计算出TTI。计算方法是:找到FCP之后的第一个安静窗口,窗口前的最后一个long task结束时间就是TTI。
安静窗口需满足三个条件:1.没有long task;2.窗口内get请求不超过2个;3.窗口时间不少于5s。
// 获取FID
new PerformanceObserver(function(list, obs) {
const firstInput = list.getEntries()[0];
// Measure the delay to begin processing the first input event.
const firstInputDelay = firstInput.processingStart - firstInput.startTime;
// Measure the duration of processing the first input event.
// Only use when the important event handling work is done synchronously in the handlers.
const firstInputDuration = firstInput.duration;
// Obtain some information about the target of this event, such as the id.
const targetId = firstInput.target ? firstInput.target.id : 'unknown-target';
// Process the first input delay and perhaps its duration...
// Disconnect this observer since callback is only triggered once.
obs.disconnect();
}).observe({type: 'first-input', buffered: true});
4.4. 采集long task
// 获取long task
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
console.log(`startTime=${entry.startTime},duration=${entry.duration}`);
}
}).observe({type: 'longtask', buffered: true});
4.5. 性能测试工具
以下是一些常用的性能测试工具:
- lighthouse
Lighthouse 是 Google 开发的一款性能测试工具。支持Chrome Extension手动使用,还可以通过 Node CLI 集成到流水线自动化测试。
- www.webpagetest.org
如果要测试国外网站,可以用webpagetest。
5. 性能基线
根据用户对网页性能的感知,有了FCP、LCP、TTI、FID等性能指标来衡量网页性能。那么,如何划分优劣呢?为此,Google提供了一个标准的性能基线。我们可以参考这个基线来监控网页性能的达标率。需要注意的是,随着软硬件的升级,基线本身是会有调整的。另外,设备性能和网络环境对网页性能也有很大影响,例如IOS上报的性能指标一般要优于安卓。
指标 | 好 | 中 | 差 |
---|---|---|---|
FCP | [0,1800ms] | [1800ms,3000ms] | >3000ms |
LCP | [0,2500ms] | [2500ms,4000ms] | >4000ms |
TTI | [0,3800ms] | [3800ms,7300ms] | >7300ms |
FID | [0,100ms] | [100ms,300ms] | >300ms |
6. 总结
前端性能对用户体验很重要。如果页面加载太慢,用户就会失去耐心而离开。页面加载出来后,用户会下意识地去操作页面,如果页面没有及时给出反馈,用户就会感知到页面延迟。因此,针对用户对性能地感知,可通过FCP、LCP、FID、TTI、long task、input delay等性能指标,衡量页面性能。通过采集性能数据,有效地帮助分析网页性能,提升性能,提升用户体验。
参考资料
海愚-如何重新认知性能优化及其度量方法
MDN Web Performance
Web Performance Working Group
前端监控系列3 | 如何衡量一个站点的性能好坏
深入浅出 Performance 工具 & API