搞懂时间分片渲染!



作者:Jolyne_ 

https://juejin.cn/post/7282756858174980132

在经典的面试题中:”如果后端返回了十万条数据要你插入到页面中,你会怎么处理?

除了像 useVirtualList 这样的虚拟列表来处理外,我们还可以通过 时间分片 来处理

通过 setTimeout

直接上一个例子:

<!--
 * @Author: Jolyne
 * @Date: 2023-09-22 15:45:45
 * @LastEditTime: 2023-09-22 15:47:24
 * @LastEditors: Jolyne
 * @Description: 
-->

<!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>十万数据渲染</title>
</head>

<body>
  <ul id="list-container"></ul>

  <script>
    const oListContainer = document.getElementById('list-container')

    const fetchData = () => {
      return new Promise(resolve => {
        const response = {
          code0,
          msg'success',
          data: [],
        }

        for (let i = 0; i < 100000; i++) {
          response.data.push(`content-${i + 1}`)
        }

        setTimeout(() => {
          resolve(response)
        }, 100)
      })
    }

    // 模拟请求后端接口返回十万条数据
    // 渲染 total 条数据中的第 page 页,每页 pageCount 条数据
    const renderData = (data, total, page, pageCount) => {
      // base case -- total 为 0 时没有数据要渲染 不再递归调用
      if (total <= 0return

      // total 比 pageCount 少时只渲染 total 条数据
      pageCount = Math.min(pageCount, total)

      setTimeout(() => {
        const startIdx = page * pageCount
        const endIdx = startIdx + pageCount
        const dataList = data.slice(startIdx, endIdx)

        // 将 pageCount 条数据插入到容器中
        for (let i = 0; i < pageCount; i++) {
          const oItem = document.createElement('li')
          oItem.innerText = dataList[i]
          oListContainer.appendChild(oItem)
        }

        renderData(data, total - pageCount, page + 1, pageCount)
      }, 0)
    }

    fetchData().then(res => {
      renderData(res.data, res.data.length, 0200)
    })

  
</script>
</body>

</html>

上面的例子中,我们使用了 setTimeout,在每一次宏任务中插入一页数据,然后设置多个这样地宏任务,直到把所有数据都插入为止。

但是很明显能看到的问题是,快速拖动滚动条时,数据列表中会有闪烁的情况

这是因为:

当使用 setTimeout 来拆分大量的 DOM 插入操作时,虽然我们将延迟时间设置为 0ms,但实际上由于 JavaScript 是单线程的,任务执行时会被放入到事件队列中,而事件队列中的任务需要等待当前任务执行完成后才能执行。所以即使设置了 0ms 延迟,setTimeout 的回调函数也不一定会立即执行,可能会受到其他任务的阻塞。

setTimeout 的回调函数执行的间隔超过了浏览器每帧更新的时间间隔(一般是 16.7ms),就会出现丢帧现象。丢帧指的是浏览器在更新页面时,没有足够的时间执行全部的任务,导致部分任务被跳过,从而导致页面渲染不连续,出现闪烁的情况

所以,我们改善一下,通过 requestAnimationFrame 来处理

通过 requestAnimationFrame

<!--
 * @Author: Jolyne
 * @Date: 2023-09-22 15:45:45
 * @LastEditTime: 2023-09-22 15:47:24
 * @LastEditors: Jolyne
 * @Description: 
-->

<!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>直接插入十万条数据</title>
</head>

<body>
  <ul id="list-container"></ul>

  <script>
    const oListContainer = document.getElementById('list-container')

    const fetchData = () => {
      return new Promise(resolve => {
        const response = {
          code0,
          msg'success',
          data: [],
        }

        for (let i = 0; i < 100000; i++) {
          response.data.push(`content-${i + 1}`)
        }

        setTimeout(() => {
          resolve(response)
        }, 100)
      })
    }

    // 模拟请求后端接口返回十万条数据
    // 渲染 total 条数据中的第 page 页,每页 pageCount 条数据
    const renderData = (data, total, page, pageCount) => {
      // base case -- total 为 0 时没有数据要渲染 不再递归调用
      if (total <= 0return

      // total 比 pageCount 少时只渲染 total 条数据
      pageCount = Math.min(pageCount, total)

      requestAnimationFrame(() => {
        const startIdx = page * pageCount
        const endIdx = startIdx + pageCount
        const dataList = data.slice(startIdx, endIdx)

        // 将 pageCount 条数据插入到容器中
        for (let i = 0; i < pageCount; i++) {
          const oItem = document.createElement('li')
          oItem.innerText = dataList[i]
          oListContainer.appendChild(oItem)
        }

        renderData(data, total - pageCount, page + 1, pageCount)
      })
    }

    fetchData().then(res => {
      renderData(res.data, res.data.length, 0200)
    })

  
</script>
</body>

</html>

很明显,闪烁的问题被解决了

这是因为:

requestAnimationFrame 会在浏览器每次进行页面渲染时执行回调函数,保证了每次任务的执行间隔是稳定的,避免了丢帧现象。所以在处理大量 DOM 插入操作时,推荐使用 requestAnimationFrame 来拆分任务,以获得更流畅的渲染效果

相关推荐

  • 【阿里】如何实现一个 async/await
  • 代码理解技术应用实践介绍
  • GPT 对外开放,先到先得
  • 编写 if 时尽量不要带 else
  • 对三个模型生成图像的多样性进行了画鸟测试,Midjourney胜
  • 1018.AI日报:欧盟新规将大模型划分为三个级别
  • 编写一个 “Hello World” 的 Web 服务器,Go、Node.js、Nim 和 Bun 谁更快?
  • 中国大模型掌门人首次集结、全球研发中心掌门人齐聚现场,1024 程序员节「岳麓对话」重磅官宣!
  • 如何在亚马逊少走弯路?从DIY定制生意开始
  • 北大机器人当上亚运志愿者,全靠学生把多模态大模型结合具身智能
  • 算法人才vs工程人才,大模型时代谁更重要?|对话澜舟科技
  • 超火迷你GPT-4视觉能力暴涨,GitHub两万星,华人团队出品
  • 大模型搞“人肉搜索”,准确率高达95.8%!研究作者:已提醒OpenAI谷歌Meta
  • Nature发文:该放开「一稿多投」了
  • 马斯克一觉醒来,纯视觉NOA已在中国开跑:上海闹市一镜到底0接管,高速城区全都不用自己开
  • LeCun和Bengio“吵”起来了,人工智能是“潘多拉魔盒”吗?
  • 让大模型真正学会1+1=2!谷歌教会模型自动学习推理规则,大模型的幻觉有救了
  • 偏偏不信文心大模型4.0比肩GPT-4!我为它们安排了一场龙虎斗!
  • 研发深恶痛绝,业界持续热捧,DDD 到底是啥?
  • 程序员真实薪资变化