不久前有对华为云官网用户性能数据收集和前端监控(juejin.cn/post/699276…) 做了一个很简单的概括,深觉网站性能数据采集对于监控网站的和优化性能有着很好的指导作用。其实我们对于检测网站性能都是基于 chrome开发团队提出了一系列性能指标:
- FP(first-paint),从页面加载开始到第一个像素绘制到屏幕上的时间
- FCP(first-contentful-paint),从页面加载开始到页面内容的任何部分在屏幕上完成渲染的时间
- LCP(largest-contentful-paint),从页面加载开始到最大文本块或图像元素在屏幕上完成渲染的时间
- CLS(layout-shift),从页面加载开始和其生命周期状态变为隐藏期间发生的所有意外布局偏移的累积分数
这四个性能指标都可以通过 PerformanceObserver 来获取(performance.getEntriesByName()
也可以用来获取,但它不是在事件触发时通知的)。PerformanceObserver 是一个性能监测对象,可以用于监听记录 performance 数据的行为,一旦记录了就会触发回调,这样我们就可以在回调里把这些数据上报。
FP
FP(first-paint)是指从页面加载开始到第一个像素绘制到屏幕上的时间
。即我们经常所说的白屏时间
。
测量代码如下:
const handlerFn = (list) => {
for (const entry of list.getEntries()) {
if (entry.name === 'first-paint') {
observer.disconnect()
}
console.log(entry)
}
}
const observer = new PerformanceObserver(handlerFn)
// buffered 属性表示是否观察缓存数据,也就是说观察代码添加时机比事情触发时机晚也没关系。
observer.observe({ type: 'paint', buffered: true })
通过以上代码可以得到 FP 的内容:
{
duration: 0,
entryType: "paint",
name: "first-paint",
startTime: 359, // fp 时间
}
其中 startTime
就是我们要的绘制时间。
FCP
FCP(first-contentful-paint)是指从页面加载开始到页面内容的任何部分在屏幕上完成渲染的时间
。这里所说的任何部分
是指某一个完整的页面模块(文本、图像、背景图、svg或者canvas元素),如下图中第二张图就是有一个完整模块出现,算是完成FCP。为了提供良好的用户体验,FCP 的分数一般应该控制在 1.8 秒以内。
测量代码:
const handlerFn = (list) => {
for (const entry of list.getEntries()) {
if (entry.name === 'first-contentful-paint') {
observer.disconnect()
}
console.log(entry)
}
}
const observer = new PerformanceObserver(handlerFn)
observer.observe({ type: 'paint', buffered: true })
通过以上代码可以得到 FCP 的内容:
{
duration: 0,
entryType: "paint",
name: "first-contentful-paint",
startTime: 459, // fcp 时间
}
其中 startTime
就是我们要的绘制时间。
LCP
LCP(largest-contentful-paint)是指页面加载开始到最大文本块或图像元素在屏幕上完成渲染的时间
。
一个良好的 LCP 分数应该控制在 2.5 秒以内。如下图所示第二张图完成了最大文本块或图像的绘制。
测量代码:
const entryHandler = (list) => {
if (observer) {
observer.disconnect()
}
for (const entry of list.getEntries()) {
console.log(entry)
}
}
const observer = new PerformanceObserver(entryHandler)
observer.observe({ type: 'largest-contentful-paint', buffered: true })
通过以上代码可以得到 LCP 的内容:
{
duration: 0,
element: p,
entryType: "largest-contentful-paint",
id: "",
loadTime: 0,
name: "",
renderTime: 1021.299,
size: 37932,
startTime: 1021.299,
url: "",
}
其中 startTime
就是我们要的绘制时间。FCP 和 LCP 的区别是:FCP 只要任意内容绘制完成就触发,LCP 是最大内容渲染完成时触发
。
CLS
CLS(Cumulative Layout Shift)是指从页面加载开始和其生命周期状态变为隐藏期间发生的所有意外布局偏移的累积分数
。预料之外的页面内容的移动经常发生,因为资源的异步加载,或者dom元素的动态添加。罪魁祸首有可能是未知尺寸的图片或者视频,或者字体文件(比默认字体大或者小),亦或是第三方广告或者挂件(会自动改变大小)。网站越复杂,就越有可能获得高CLS分数,对于标志性的Google主页等简单布局,没有CLS,因为它们包含的元素很少。这并不是说所有复杂的网站都有很高的CLS分数,以亚马逊为例。没有人会说这家电子商务巨头使用简单的网页设计。然而,在浏览其目录时几乎看不到布局变化。
- 1.CLS对网站性能有什么影响? 从理论上讲,你可以有一个非常快的网站,但仍然会有一个相对较差的CLS分数。与其他核心网络指标一样,CLS分数可能与网站的整体性能没有直接关系。你的网站可能快得惊人,但当它加载时,它就像一个折叠框一样展开了,大的布局变化肯定会对网站的用户体验产生负面影响。谷歌认为这一点非常重要。这就是为什么谷歌对这个数据点的权重如此之高。
- 2.CLS的重要性是否低于FCP或LCP的分数? 很多用户更关注FCP和LCP的分数。这是因为这两个指标更容易与网站性能联系起来。尽管FCP也是一个专门以用户为中心的指标,但CLS更难在大量的用户中进行一致的测量。这三个指标才是构成了谷歌核心网络指标。如果你忽视了其中一个指标,你就有可能在相关搜索结果中排名靠后。确保你的网站是为低CLS而优化的,一般来说,对网站性能(如LCP和FCP)没有什么积极影响。如果用户一看到 "最大的内容"(Largest Contentful Paint)就被推到屏幕下面,那么快速加载时间又有多大用处呢?
布局偏移分数的计算方式如下:
布局偏移分数 = 影响分数 * 距离分数
[影响分数]测量不稳定元素对两帧之间的可视区域产生的影响。
[距离分数]指的是任何不稳定元素在一帧中位移的最大距离(水平或垂直)除以可视区域的最大尺寸维度(宽度或高度,以较大者为准)。
那么我们怎么来计算 CLS 的值呢?在 CLS 中,定义了一个术语叫 会话窗口 —— 一个或多个快速连续发生的单次布局偏移,每次偏移相隔的时间少于 1 秒,且整个窗口的最大持续时长为 5 秒
。例如下图中的第二个会话窗口,它里面有四次布局偏移,每一次偏移之间的间隔必须少于 1 秒,并且第一个偏移和最后一个偏移之间的时间不能超过 5 秒,这样才能算是一次会话窗口。如果不符合这个条件,就算是一个新的会话窗口。
CLS 一般通过取所有会话窗口中的最大值来计算。这种方式是目前最优的计算方式,每次只取所有会话窗口的最大值,用来反映页面布局偏移的最差情况
。
let sessionValue = 0
let sessionEntries = []
const cls = {
subType: 'layout-shift',
name: 'layout-shift',
type: 'performance',
pageURL: getPageURL(),
value: 0,
}
const entryHandler = (list) => {
for (const entry of list.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(formatCLSEntry(entry))
} else {
sessionValue = entry.value
sessionEntries = [formatCLSEntry(entry)]
}
// 当前sessionValue值更大就取该值
if (sessionValue > cls.value) {
cls.value = sessionValue
cls.entries = sessionEntries
cls.startTime = performance.now()
lazyReportCache(deepCopy(cls))
}
}
}
}
const observer = new PerformanceObserver(entryHandler)
observer.observe({ type: 'layout-shift', buffered: true })
在看完上面的文字描述后,再看代码就好理解了。一次布局偏移的测量内容如下:
{
duration: 0,
entryType: "layout-shift",
hadRecentInput: false,
lastInputTime: 0,
name: "",
sources: (2) [LayoutShiftAttribution, LayoutShiftAttribution],
startTime: 1176.199999999255,
value: 0.000005752046026677329,
}
代码中的 value
字段就是布局偏移分数。