高性能渲染十万条数据不卡顿【虚拟滚动】

简介: 高性能渲染十万条数据不卡顿【虚拟滚动】

前端学习路线一条龙【内含入门到进阶到高级精选资源】无套路获取!!!

前端小菜鸡之菜鸡互啄,公众号:前端开发爱好者xy哥怒肝,前端学习路线一条龙【内含入门到进阶到高级精选资源】无套路获取!!!

哈喽,大家好 我是xy👨🏻‍💻。最近在业务开发中遇到了数据量大但不能使用分页方式来加载的列表,如果完整渲染这种列表会非常卡顿,体验感极差。那么有什么办法能够优化这类问题?

问题思考

页面为什么会卡顿 ?难道是数据量太大,js 运算不过来了吗???

带着这样的问题,到页面尝试下,直接在页面中一次性插入十万条数据

<ul id="test"></ul>
// 记录任务开始时间
let now = Date.now();
// 定义十万条数据
const total = 100000;
// 获取容器
let ul = document.getElementById('test');
// 将数据插入容器 #test 中
for (let i = 0; i < total; i++) {
    let li = document.createElement('li');
    li.innerText = '测试数据'+ i
    ul.appendChild(li);
}
console.log('JS运行时间:',Date.now() - now);
// JS运行时间:187
setTimeout(()=>{
  console.log('总运行时间:',Date.now() - now);
  // 总运行时间:2844
},0)

我们对十万条记录进行循环操作,JS 的运行时间为 187ms,还是蛮快的,但是最终渲染完成后的总时间是 2844ms

在 JS 的 Event Loop 中,当 JS 引擎所管理的执行栈中的事件以及所有微任务事件全部执行完后,才会触发渲染线程对页面进行渲染

如果还不了解 js Event Loop 的同学,建议自行查询补充下基础知识,这里就不做过多总结,通过以上两次console能够得出结论:

对于大量数据渲染的时候,JS 运算并不是性能的瓶颈,性能的瓶颈主要在于渲染阶段

既然是渲染的锅,那我们就可以从渲染方面去优化:可视区域渲染-只渲染可见部分,不可见部分不渲染

虚拟滚动列表

虚拟滚动列表就是采用的可视区渲染方式优化,是一种根据滚动容器元素的可视区域来渲染长列表数据中某一个部分数据的技术。

首先要明白两个概念:

  • 可视区域:滚动列表的容器,可以纵向滚动,其视觉可见的区域就称之为可视区域(简而言之就是你滚动容器的高度)
  • 列表区域:滚动容器元素的内部内容区域。假设有 100 条数据,每个列表项的高度是 30,那么可滚动的区域的高度就是 100 * 30。可滚动区域当前的具体高度值一般可以通过(滚动容器)元素的 scrollHeight 属性获取。用户可以通过滚动来改变列表在可视区域的显示部分

image.png

代码实现

用数组保存列表所有元素的位置,只渲染可视区域内的列表元素,当可视区滚动时,根据滚动的 offset 大小以及所有列表元素的位置,计算在可视区应该渲染哪些元素。

实现步骤:

  • 计算当前可见区域起始数据的 startIndex
  • 计算当前可见区域结束数据的 endIndex
  • 计算当前可见区域的数据,并渲染到页面中
  • 计算 startIndex 对应的数据在整个列表中的偏移位置 startOffset,并设置到列表上
  • 计算 endIndex 对应的数据相对于可滚动区域最底部的偏移位置 endOffset,并设置到列表上

用代码简单实现:

vue 版本:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <style>
    .list-view {
      height: 400px;
      overflow: auto;
      position: relative;
      border: 1px solid #aaa;
    }
    .list-view-phantom {
        /* 使用不可见区域,撑起这个列表,让列表的滚动条出现 */
      position: absolute;
      left: 0;
      top: 0;
      right: 0;
      z-index: -1;
    }
    .list-view-content {
      left: 0;
      right: 0;
      top: 0;
      position: absolute;
    }
    .list-view-item {
      padding: 5px;
      color: #666;
      line-height: 30px;
      box-sizing: border-box;
    }
    [v-cloak] {
      display: none;
    }
  </style>
  <body>
    <div id="app" v-cloak>
      <div class="list-view" ref="scrollBox" @scroll="handleScroll">
        <div
          class="list-view-phantom"
          :style="{height: contentHeight}"
        ></div>
        <div ref="content" class="list-view-content">
          <div
            class="list-view-item"
            :style="{height: itemHeight + 'px'}"
            v-for="item in visibleData"
          >
            {{ item }}
          </div>
        </div>
      </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
      new Vue({
        el: "#app",
        computed: {
          contentHeight() {
            return this.data.length * this.itemHeight + "px";
          },
        },
        mounted() {
          this.updateVisibleData();
        },
        data() {
          return {
            data: new Array(100).fill(1),
            itemHeight: 30, // 列表每行的行高
            visibleData: [],
          };
        },
        methods: {
          updateVisibleData(scrollTop = 0) {
            const visibleCount = Math.ceil(
              this.$refs.scrollBox.clientHeight / this.itemHeight
            );
            const start = Math.floor(scrollTop / this.itemHeight);
            const end = start + visibleCount;
            this.visibleData = this.data.slice(start, end); // 计算可见区域数据
            this.$refs.content.style.webkitTransform = `translate3d(0, ${
              start * this.itemHeight
            }px, 0)`;
          },
          handleScroll() {
            const scrollTop = this.$refs.scrollBox.scrollTop;
            this.updateVisibleData(scrollTop);
          },
        },
      });
    </script>
  </body>
</html>

react 版本:

// index.tsx
import React from 'react';
import VirtualList from './VirtualList';
const listdata = new Array(100000).fill(0);
<VirtualList
  width='100%'
  height={300}
  itemCount={listdata.length}
  itemSize={50}
  renderItem={(data: any) => {
    const { index, style } = data;
    return (
      <div key={index} style={{
        ...style,
        backgroundColor: index % 2 === 0 ? '#fff' : '#eee'
      }}>
      {index + 1}
  </div>
  )
  }}
 />
// VirtualList.tsx
import React, { useRef, useState, useMemo } from 'react';
interface VirtualListProps {
  width: string | number;
  height: number;
  itemCount: number;
  itemSize: number;
  renderItem: (...args: any) => JSX.Element;
}
const VirtualList: React.FC<VirtualListProps> = ({
  width,
  height,
  itemCount,
  itemSize,
  renderItem,
}) => {
  const scrollBox = useRef({} as HTMLDivElement);
  const [start, setStart] = useState(0);
  const end = useMemo(() => {
    const endIndex = start + Math.floor(height / itemSize) + 1;
    return endIndex > itemCount ? itemCount : endIndex;
  }, [start]);
  const visibleList = useMemo(() => {
    return new Array(end - start).fill(0).map((item, index)=>({ item, index: start + index }));
  }, [start, end]);
  const styles = useMemo(
    () => ({
      position:'absolute',
      top:0,
      left:0,
      width:'100%',
      height: itemSize,
    }),
    [itemSize]
  );
  const handleScroll = () => {
    const startIndex = Math.floor(scrollBox.current.scrollTop / itemSize);
    setStart(startIndex);
  };
  return (
    <div
      ref={scrollBox}
      style={{ overflow: 'auto', willChange: 'transform', width, height, border: '1px solid #000' }}
      onScroll={handleScroll}
    >
      <div style={{ position: 'absolute',width:'100%',height: `${itemCount * itemSize}px` }}>
        {
          visibleList.map(({ index }) => renderItem({ index, style: { ...styles, top: itemSize * index } }))
        }
      </div>
    </div>
  )
};
export default VirtualList;
相关文章
|
6月前
|
Windows
U3D引擎虚拟仿真课程加载缓慢怎么解决?实时渲染技术
针对以上问题,既要考虑原有资源的利旧使用,也要考虑用户使用的流畅体验。实时渲染云流化技术方案,可以很好地解决这两个问题。因为点量云流实时渲染系统,不仅仅是针对U3D/UE引擎,还可以是webgl网页的流化,直接将整个浏览器流化给用户来使用。这样可以将这些原来比较老的webgl课程放在服务器端,为服务器配置高性能的显卡和CPU ,在教学或者使用过程中直接使用服务器算力,用户侧只需要普通的电脑、平板等轻终端设备即可实时使用这些课程。而且高性能的显卡,一般可以支持数十个用户同时使用,可能一台服务器1-2张显卡就可以满足30-40个人使用(这里只是预估,具体以实际为准)。
48 0
|
6月前
|
编解码 移动开发 前端开发
【面试题】 给你十万条数据,怎么样顺滑的渲染出来?
【面试题】 给你十万条数据,怎么样顺滑的渲染出来?
|
3月前
|
C# UED 开发者
WPF与性能优化:掌握这些核心技巧,让你的应用从卡顿到丝滑,彻底告别延迟,实现响应速度质的飞跃——从布局到动画全面剖析与实例演示
【8月更文挑战第31天】本文通过对比优化前后的方法,详细探讨了提升WPF应用响应速度的策略。文章首先分析了常见的性能瓶颈,如复杂的XAML布局、耗时的事件处理、不当的数据绑定及繁重的动画效果。接着,通过具体示例展示了如何简化XAML结构、使用后台线程处理事件、调整数据绑定设置以及利用DirectX优化动画,从而有效提升应用性能。通过这些优化措施,WPF应用将更加流畅,用户体验也将得到显著改善。
185 0
|
5月前
|
缓存 前端开发 JavaScript
【前端性能优化】深入解析重绘和回流,构建高性能Web界面
【前端性能优化】深入解析重绘和回流,构建高性能Web界面
68 1
|
6月前
|
前端开发 物联网 异构计算
实时云渲染串流技术详解
云串流即使应用于云旅游、考古、数字孪生、云展厅等领域,通过将3D应用运行于云端,降低用户端配置需求。技术流程包括用户股指令、服务器执行、编码传输及前端播放,实现低延迟的实时云渲染。在弱网环境下,需只能调节画质确保流畅性。3D应用采用云推理,服务器需满足3D应用的硬件要求,尤其是GPU和GPU的性能,而前端主要负责解码播放,一般1080P视频能力即可。自行研发成本高,建议选择成熟商家点量云流。
202 0
实时云渲染串流技术详解
|
6月前
|
编解码 人工智能 JavaScript
如果高性能渲染十万条数据?
如果高性能渲染十万条数据?
|
11月前
|
编解码 运维 5G
实时云渲染与本地渲染:优劣对比与未来趋势
实时云渲染与本地渲染:优劣对比与未来趋势
|
编解码 光互联
关于云流化系统-实时云渲染延时性的讨论
时云渲染系统来做程序的流化,是将程序放在服务器上,用户终端的各种操作指令完成都是借助的服务器算力。而为了用户能拥有和本地安装类似的体验效果,指令执行和传回终端的时间就必须尽可能短,这是实时云渲染系统很重要的一个参数:延迟性。没有延迟,该方案就无法落地
231 0
关于云流化系统-实时云渲染延时性的讨论