1、 背景
特别说明,今天讲两个哈:
- TBT(Total Blocking Time):总阻塞时间,即页面主线程被阻塞的总时间。
- CLS(Cumulative Layout Shift):累计布局偏移,即页面上所有元素在视觉上发生的意外移动的总和
使用chatGPT,设计一套可落地的前端性能优化方案(下)- LCP 的优化方案 - 掘金 (juejin.cn)
本篇呢,继续上一篇的内容,继续调研性能指标的优化方案,定两个主要方向:
- 编译时 —— 构建工具打包时的优化(我主要站webpack队的,以下优化方案,如果没有特别说明,均是webpack方案)
- 运行时 —— 用户从输入URL到展示指标以及对应的优化方案
根据最新的chrome Lighthouse规则,前端的性能指标主要有:
- FCP(First Contenful Paint):首次内容绘制时间,即浏览器首次绘制任何文本、图像、非空白canvas或SVG的时间。
- SI(Speed Index):速度指数,即页面渲染速度的指标。
- LCP(Largest Contentful Paint):最大内容绘制时间,即页面中最大的可见内容元素绘制完成的时间。
- TBT(Total Blocking Time):总阻塞时间,即页面主线程被阻塞的总时间。
- CLS(Cumulative Layout Shift):累计布局偏移,即页面上所有元素在视觉上发生的意外移动的总和。
- TTI(Time to Interactive)TTI是指页面变得可交互所需的时间
- TTD(Time to Display)TTD是指页面显示所需的时间
2、 TBT(Total Blocking Time):总阻塞时间,即页面主线程被阻塞的总时间。
问题:前端性能中TBT的优化目标是什么?你能给我一些优化TBT的具体方法吗?
问题差不多就这些,我们简单总结一下:
- TBT的优化目标是将总阻塞时间控制在300毫秒以内,以提供良好的用户体验。
- TBT的计算方法是在首次内容绘制(FCP)与可交互时间(TTI)之间,所有执行耗时大于50毫秒的任务(宏任务),大于50毫秒那部分时间的总和。
- 优化TBT的方法主要有减少第三方代码的影响,减少JavaScript执行时间,最小化主线程工作,保持较低的请求数和较小的传输大小。
关于优化方案我们提取一下:
- 减少第三方代码的影响,尽量避免使用不必要的第三方库或插件,或者将它们异步加载或延迟加载。
- 保持较低的请求数和较小的传输大小,合并或压缩资源文件,使用缓存或CDN加速资源加载,减少网络传输时间。
- 减少JavaScript执行时间,优化业务逻辑代码,去掉无效代码,使用更高效的算法或数据结构,避免重复计算或不必要的操作。
- 最小化主线程工作,将一些耗时的任务分片执行,利用requestIdleCallback或setTimeout将任务拆分为多个小于50毫秒的子任务,或者将任务移动到Web Worker中执行。
大部分优化方案,在前文中都有提到,这里不再重复,我们主要研究最后两项:
2.1 减少JavaScript执行时间,优化业务逻辑代码,去掉无效代码,使用更高效的算法或数据结构,避免重复计算或不必要的操作
需要注意的是,这里面大部分优化都需要在codereview的工作来处理,当前情况review工作,虽然可以使用eslint、prettier完成一部分工作,但仍需要人去参与,我大概总结了两点:
- eslint、prettier 用来保持风格和规范,降低人review代码的成本
- review代码的作用,优化业务逻辑,去掉无效代码,使用更高效的算法或数据结构,避免重复计算或不必要的操作,提高前端性能
2.2 最小化主线程工作,将一些耗时的任务分片执行,利用requestIdleCallback或setTimeout将任务拆分为多个小于50毫秒的子任务,或者将任务移动到Web Worker中执行
这个的原理很简单,主要是用了requestIdleCallback或setTimeout的API来拆分高开销计算工作
我假设你想要将一个长时间运行的函数分片执行,这个函数接受一个参数并返回一个结果。请看下面的代码:
/ 定义一个长时间运行的函数
function longRunningFunction(arg) {
// 这里写你的逻辑
// 返回一个结果
}
// 定义一个分片执行的函数
function chunkedFunction(arg, callback) {
// 创建一个任务队列
let taskQueue = [];
// 将长时间运行的函数拆分为多个子任务,每个子任务不超过50毫秒
// 将子任务添加到任务队列中
// 这里写你的逻辑
// 创建一个变量来存储最终结果
let result = null;
// 创建一个函数来执行任务队列中的下一个子任务
function nextTask() {
// 如果任务队列为空,说明所有子任务都已完成
if (taskQueue.length === 0) {
// 调用回调函数并传入最终结果
callback(result);
return;
}
// 从任务队列中取出第一个子任务
let task = taskQueue.shift();
// 执行子任务,并将返回值累加到最终结果中
// 这里写你的逻辑
// 利用requestIdleCallback或setTimeout来安排下一个子任务的执行
// 这里写你的逻辑
}
// 开始执行第一个子任务
nextTask();
}
// 调用分片执行的函数,并传入一个参数和一个回调函数
chunkedFunction("some argument", function (result) {
// 在回调函数中处理最终结果
console.log(result);
});
3、CLS(Cumulative Layout Shift):累计布局偏移,即页面上所有元素在视觉上发生的意外移动的总和
问题:前端性能中CLS的优化目标是什么?有没有具体数值的优化指标?如何优化CLS
简单总结一下,CLS是一个衡量页面视觉稳定性的性能指标,它反映了页面元素在加载过程中的位置变化。CLS越低,页面越稳定,用户体验越好。优化CLS的方法主要有为媒体元素指定尺寸、为动态内容预留空间、控制自定义字体的加载、使用transform属性实现动画和使用工具检测和分析布局偏移等。
CLS的优化指标是0.1以下,也就是说,页面元素的位置变化应该占可视区域的不到10%
这里需要注意的是,这个值是个累计值,计算的是从加载到离开,但离开的事件不太容易被监听到(比如用户直接结束浏览器进程)
面对这样的问题,我们可以监控用户的事件,简单说明一下:
- 用户于页面发生交互,从而产生事件
- 事件的产生,有可能导致页面变动,从而产生CLS
- 我们在body上代理全部事件,并把当时的CLS保存在用户本地,在下次打开时传回后端(虽然有可能丢失一部分数据,但可以节约网站开销)
- 页面元素的位置变化应该占可视区域的不到10%
关于优化点
优化CLS的方法主要有以下几个:
- 为图片、视频等媒体元素指定宽度和高度属性,或者使用CSS的max-width和height:auto属性来适应不同的视口大小。
- 为动态内容(如广告、弹窗等)预留足够的空间,或者将它们放在页面的边缘,避免影响其他元素的布局。
- 使用字体显示控制(font-display)属性来控制自定义字体的加载行为,避免字体变化导致的布局偏移。
- 使用transform属性来实现动画效果,而不是使用margin、padding等会影响布局的属性。
- 使用Chrome开发者工具或PageSpeed Insights等工具来检测和分析页面上导致布局偏移的元素,并进行相应的优化。
大部分优化点都很简单,这里就不进行累述了,各位同学开发中多注意即可。
4、小结
好的针对TBT和CLS的优化,应该就这些了,有一大部分的优化跟FCP、SI、LCP是重复的,这里我们再重复一下目标。
TBT 宏任务执行间隔小于50ms CLS的优化指标是0.1以下,也就是说,页面元素的位置变化应该占可视区域的不到10%
这里特别注意,CLS的收集是累计的,并不是在页面最开始的时候收集的,由于离开事件不容易被监听到,我们可以在body下代理大部分事件,把CLS存入用户本地,下一打开时在提交到日志系统。
由于篇幅所限,其他的性能指标放在下一篇来分享
本文正在参加 人工智能创作者扶持计划