前端虚拟滚动列表 vue虚拟列表

前端虚拟滚动列表

在大型的企业级项目中经常要渲染大量的数据,这种长列表是一个很普遍的场景,当列表内容越来越多就会导致页面滑动卡顿、白屏、数据渲染较慢的问题;大数据量列表性能优化,减少真实dom的渲染


看图:绿色是显示区域,绿色和蓝色中间属于预加载:解决滚动闪屏问题;大致了解了流程在往下看;
在这里插入图片描述

实现效果:

先说一下你看到这么多真实dom节点是因为做了预加载,减少滚动闪屏现象,这里写了300行,可以根据实际情况进行截取
在这里插入图片描述

实现思路:

虚拟列表滚动大致思路:两个div容器

  外层:外部容器用来固定列表容器的高度,同时生成滚动条

  内层:内部容器用来装元素,高度是所有元素高度的和

  外层容器鼠标滚动事件  dom.scrollTop 获取滚动条的位置

  根据每行列表的高以及当前滚动条的位置,利用slice() 去截取当前需要显示的内容

  重点:滚动条的高度是有内层容器的paddingBottom 和 paddingTop 属性顶起来了,确保滚动条位置的准确性

  这里鼠标上下滚动会出现闪屏问题:解决方案如下:

      方案一:  预加载:

                    向下预加载:
                        比如div滚动区域显示30行,就预加载 300行( 即这里 slice(startIndex,startIndex + 300) ),

                    向上预加载:
                        在滚动监听事件函数中(computeRow)判断inner的paddingTop和paddingBottom即可

                    当然这里的download-box的padding有30px像素,在加一个div,overflow:hidded就解决了

      方案二:缩小滚动范围或者节流时间缩短,这里写的500ms

具体代码

  <template>
    <div class="enn">
      <div class="download-box txt" id="scrollable-div" @scroll="handleScroll">
        <div id="inner">
          <div v-for="(item, index) in data2" :key="index" class="line-box">
            <div :class="{ 'text-box': props.collapsed, 'text-box-samll': !props.collapsed }">
              {{ item }}
            </div>
          </div>
        </div>
      </div>
    </div>
  </template>

  <script lang="ts" setup>
  import { onMounted, PropType, ref } from 'vue';

  import { useText } from './hooks/useText';

  const props = defineProps({
    baseData: {
      type: Object as PropType<{
        taskId: string;
        barcodeName: string;
      }>,
      default: {},
    },
    collapsed: {
      type: Boolean,
      default: true,
    },
    type: {
      type: Boolean,
      default: false,
    },
  });

  const { data } = useText(props.type);

  //  这里大数据量数组是  data.geneTexts

  /**
   * 虚拟列表滚动大致思路:两个div容器
   *
   *    外层:外部容器用来固定列表容器的高度,同时生成滚动条
   *
   *    内层:内部容器用来装元素,高度是所有元素高度的和
   *
   *    外层容器鼠标滚动事件  dom.scrollTop 获取滚动条的位置
   *
   *    根据每行列表的高以及当前滚动条的位置,利用slice() 去截取当前需要显示的内容
   *
   *    重点:滚动条的高度是有内层容器的paddingBottom 和 paddingTop 属性顶起来了,确保滚动条位置的准确性
   *
   *    这里鼠标上下滚动会出现闪屏问题:解决方案如下:
   *
   *        方案一:  预加载:
   *
   *                      向下预加载:
   *                          比如div滚动区域显示30行,就预加载 300行( 即这里 slice(startIndex,startIndex + 300) )*
   *                      向上预加载:
   *                          在滚动监听事件函数中(computeRow)判断inner的paddingTop和paddingBottom即可
   *
   *                      当然这里的download-box的padding有30px像素,在加一个div,overflow:hidded就解决了
   *
   *        方案二:缩小滚动范围或者节流时间缩短,这里写的500ms
   *
   *
   */

  let timer_throttle: any;
  const throttle = (func: Function, wait?: number) => {
    wait = wait || 500;
    if (!timer_throttle) {
      timer_throttle = setTimeout(() => {
        func.apply(this);
        timer_throttle = null;
      }, wait);
    }
  };

  // 鼠标滚动事件
  const handleScroll = (event: any) => throttle(computeRow, 100);
  // 计算当前显示tab
  const computeRow = () => {
    // console.log('距离顶部距离', window.scrollY, geneTexts);

    let scrollableDiv = document.getElementById('scrollable-div');
    let topPosition = scrollableDiv.scrollTop;
    let leftPosition = scrollableDiv.scrollLeft;
    console.log('垂直滚动位置:', topPosition, '水平滚动位置:', leftPosition);

    const startIndex = Math.max(0, Math.floor(topPosition / 30));
   
    const endIndex = startIndex + 300;
    data2.value = data.geneTexts.slice(startIndex, endIndex);

    let inner = document.getElementById('inner');
    if (topPosition < 2700) {
      // 向上预计加载,这里判断了三个高度,可以多判断几个,增加流畅度
      inner.style.paddingTop = topPosition + 'px';
      inner.style.paddingBottom = (data.geneTexts.length + 2) * 30 - topPosition + 'px';
    } else {
      inner.style.paddingTop = topPosition - 2700 + 'px';
      inner.style.paddingBottom = (data.geneTexts.length + 2) * 30 + 2700 - topPosition + 'px';
    }
  };
  const data2 = ref([]);
  const init = () => {
    data2.value = data.geneTexts.slice(0, 300);
    let inner = document.getElementById('inner');
    inner.style.paddingTop = 0 + 'px';
    inner.style.paddingBottom = (data.geneTexts.length + 2) * 30 - 900 + 'px';
  };
  </script>

  <style lang="less" scoped>
  .button-box {
    margin-bottom: 25px;
    .flex-type(flex-end);

    :deep(.ant-btn) {
      margin-left: 10px;
    }
  }
  .enn {
    background: #282c34;
    outline: 1px solid red;
    padding: 30px 20px;
    height: 960px;
  }
  .download-box {
    width: 100%;
    // padding: 30px 20px;
    outline: 1px solid rgb(17, 0, 255);
    background-color: #fff;
    overflow: hidden;

    .line-box {
      .flex-type(flex-start);
      height: 30px;
    }

    &.txt {
      background: #282c34;
      color: #fff;
      height: 900px;
      overflow: auto;
    }
  }
  </style>

替代方案

上面是自己写的,github上面还有好多插件可以用,但各有优劣,根据自己需求选择
如:

vue-virtual-scroller

https://github.com/Akryum/vue-virtual-scroller/tree/0f2e36248421ad69f41c9a08b8dcf7839527b8c2

vue-virt-list

vue-draggable-virtual-scroll-list

virtual-list

自己找吧,我就不一一列举了,看图

在这里插入图片描述

上一篇:数据结构——栈-栈的概念及结构 


下一篇:RabbitMQ(四)-如何保证消息不丢失