使用chatGPT,设计一套可落地的前端性能优化方案(下)- TBT、CLS的优化方案

2,089 阅读8分钟

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的具体方法吗?

1680744083726.png

1680744884009.png

问题差不多就这些,我们简单总结一下:

  • 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

1680748035082.png

1680755606841.png

简单总结一下,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存入用户本地,下一打开时在提交到日志系统。

由于篇幅所限,其他的性能指标放在下一篇来分享

本文正在参加 人工智能创作者扶持计划