如何减少项目的白屏时间,优化页面的卡顿

1.如何减少项目的白屏时间,优化页面的卡顿

问题背景

在某些情况下,我们希望等待当前帧渲染完成后执行某个函数。这样可以确保在进行下一次操作之前,浏览器已经完成了渲染工作,以提供更流畅的用户体验。例如,当我们需要处理大量数据并进行渲染时,我们可以使用 requestAnimationFrame 在下一帧开始时调用处理函数,以避免阻塞主线程。

我们来看下需求,我们需要在页面中渲染多个组件元素,不断插入页面,举例如下:

<!DOCTYPE html>
<html lang="en"><head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="main" style="display: flex;flex-wrap: wrap;">
        
    </div>
</body>
<script>
    // 假设有一个包含大量数据的数组
    function processBatch() {
        for (let i = 0; i < 199999; i++) {
            // 渲染数据到页面,例如创建 DOM 元素、更新内容等操作
            const div = document.createElement('div');
            div.style.width=100+'px';
            div.style.height=100+'px';
            div.style.background = 'red';
            div.style.margin='20px';
            document.getElementById('main').appendChild(div);          
    }
    processBatch()
</script>
</html>

我们来看下这个页面的渲染过程以及Performance性能分析

 

我们可以看到在开始的时候会有一段时间的白屏卡顿,如果我们不处理的话,数据量再大,白屏时间会越来越长。

 

可以看到有两帧都有长时间的Loog task 阻塞了主任务,这就是我们需要优化的部分,因为浏览器是多线程的,但是js是单线程的,我们需要拆分长任务,可以使用webwork多线程,可以参考我上篇文章 https://www.cnblogs.com/ximenchuifa/p/17789762.html ,我们接下来主要介绍 requestAnimationFrame 渲染帧,是浏览器用于定时循环操作的一个接口,类似于setTimeout,setInterval,但是它们有着严重的性能问题,requestAnimationFrame是一个专门为实现高性能的帧动画而设计的API。

<!DOCTYPE html>
<html lang="en"><head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="main" style="display: flex;flex-wrap: wrap;">
        
    </div>
</body>
<script>
    // 假设有一个包含大量数据的数组
    const data = [/* 大量数据 */];
    for (let index = 0; index < 9999; index++) {
        data.push(index)
    }
    // 每帧处理的数据数量
    const batchSize = 100;
​
    // 当前处理的数据索引
    let currentIndex = 0;
​
    // 处理和渲染一批数据的函数
    function processBatch() {
        for (let i = 0; i < 199999; i++) {
            // 处理当前索引的数据
            const item = data[currentIndex];
            // 渲染数据到页面,例如创建 DOM 元素、更新内容等操作
            const div = document.createElement('div');
            div.style.width=100+'px';
            div.style.height=100+'px';
            div.style.background = 'red';
            div.style.margin='20px';
            document.getElementById('main').appendChild(div);
            // 增加当前索引
            currentIndex++;
​
            // 判断是否还有数据需要处理
             if (currentIndex >= data.length) {
                // 数据处理完成
                return;
             }
        }
​
        // 在下一帧继续处理下一批数据
        requestAnimationFrame(processBatch);
    }
​
    // 开始处理和渲染数据
    requestAnimationFrame(processBatch);
    processBatch()
</script>
</html>

 

再看下性能分析

 

再看下发现没有卡顿,很流畅,体验也很丝滑,性能分析也没有警告和Look task了,但是有可能总的渲染时间比起前多。在改进的代码中我们可以看到有个每帧处理的数据数量batchSize,如何确定这个值呢?

确定合适的 batchSize 大小以平衡处理时间和渲染性能是一个挑战,因为它取决于多个因素,包括数据量的大小、处理逻辑的复杂度、设备性能等。下面是一些方法和建议来帮助确定合适的 batchSize 大小:

  1. 设备性能考量:不同设备的性能不同,因此 batchSize 的大小可能需要根据目标设备进行调整。较低性能的设备可能需要较小的 batchSize,以避免过多的计算和渲染压力。

  2. 实时性需求:如果你需要实时性的渲染效果,较小的 batchSize 可能更合适。这样可以更频繁地在每一帧中进行渲染,提供更实时的更新效果。

  3. 用户体验:根据用户的反馈和感知,调整 batchSize 的大小。如果用户觉得页面渲染速度不够流畅,可以尝试减小 batchSize,反之亦然。

  4. 性能监测和优化:使用浏览器的开发者工具进行性能监测,观察每帧的处理时间和页面渲染性能。通过不同的 batchSize 大小进行测试,并分析性能指标,找到一个平衡点。

  5. 增量调整:从一个较小的 batchSize 开始,逐渐增加 batchSize 的大小,同时观察性能和用户体验的变化。一旦达到性能和用户体验的平衡点,就可以确定合适的 batchSize 大小。

请注意,batchSize 的最佳值可能因不同的应用场景和具体需求而有所不同。因此,根据实际情况进行试验、优化和调整是找到最佳 batchSize 大小的关键。

我们可以将其封装成一个通用函数。本文将介绍如何将 requestAnimationFrame 封装成一个通用函数,以便在需要的地方快速调用。

封装通用函数

下面是一个将 requestAnimationFrame 封装成通用函数的示例代码:

function startAnimationFrame(callback) {
  let isRunning = true;
​
  function frame() {
    if (!isRunning) { //取消帧的请求,结束操作
      return;
    }
​
    callback();
    requestAnimationFrame(frame);
  }
​
  requestAnimationFrame(frame);
​
  return function stop() {
    isRunning = false; //在下一次不会继续
  };
}

 

你可以将 processBatch 函数作为参数传递给 startAnimationFrame 函数,它会在每一帧开始时调用 processBatch 函数。该函数还会返回一个 stop 函数,用于停止动画循环。例如:

function processBatch() {
  // 处理和渲染数据的逻辑
}
​
const stopAnimation = startAnimationFrame(processBatch);
​
// 调用 stopAnimation 函数以停止动画循环
// stopAnimation();

 

通过上述代码,我们将 processBatch 函数作为参数传递给 onNextFrame 函数,它将在下一帧开始时调用该函数。这确保了处理函数将在当前帧渲染完成后执行,提供了更好的用户体验。

优势和注意事项

通过封装 requestAnimationFrame 成通用函数,我们可以获得以下优势:

  1. 更好的性能: 在下一帧开始时执行函数可以避免阻塞主线程,提高页面的响应性能。

  2. 更流畅的动画: 在动画场景中,使用 requestAnimationFrame 可以确保每一帧的渲染完成后再进行下一次更新,从而产生更流畅的动画效果。

posted @ 2023-11-13 19:05  小不点灬  阅读(35)  评论(0编辑  收藏  举报