• 虚拟列表


    给大家讲讲“虚拟列表”

     

    多数据渲染

    现在解决多数据渲染,相信大家可能会想到分页,触底加载,懒加载等等,但其实虚拟列表也是多数据高性能加载的一个重要解决方案。

    虚拟列表的概念

    虚拟滚动,就是根据容器可视区域列表容积数量,监听用户滑动或滚动事件,动态截取长列表数据中的部分数据渲染到页面上,动态使用空白站位填充容器上下滚动区域内容,模拟实现原生滚动效果

    image.png

    • 浏览器渲染===一次性渲染10000个肯定会使浏览器压力大,造成用户体验差
    • 容器可视区域===10000个排队去渲染,比如一次渲染10个
    • 上方下方区域===轮不到你渲染,你就乖乖进空白区待着

    实现

    基本实现

    • 可视区域的高度
    • 列表项的高度
    • 可视区域能展示的列表项个数 = ~~(可视区域高度 / 列表项高度) + 2
    • 开始索引
    • 结束索引
    • 预加载(防止滚动过快,造成暂时白屏)
    • 根据开始索引和结束索引,截取数据展示在可视区域
    • 滚动节流
    • 上下空白区使用padding实现
    • 滑动到底,再次请求数据并拼接
    <template>
      <div class="v-scroll" @scroll.passive="doScroll" ref="scrollBox">
        <div :style="blankStyle" style="height: 100%">
          <div v-for="item in tempSanxins" :key="item.id" class="scroll-item">
            <span>{{ item.msg }}</span>
            <img :src="item.src" />
          </div>
        </div>
      </div>
    </template>
    
    
    <script>
    import { throttle } from "../../utils/tools";
    export default {
      data() {
        return {
          allSanxins: [], // 所有数据
          itemHiehgt: 150, // 列表每一项的宽度
          boxHeight: 0, // 可视区域的高度
          startIndex: 0, // 元素开始索引
        };
      },
      created() {
        // 模拟请求数据
        this.getAllSanxin(30);
      },
      mounted() {
        // 在mounted时获取可视区域的高度
        this.getScrollBoxHeight();
        // 监听屏幕变化以及旋转,都要重新获取可视区域的高度
        window.onresize = this.getScrollBoxHeight;
        window.onorientationchange = this.getScrollBoxHeight;
      },
      methods: {
        getAllSanxin(count) {
          // 模拟获取数据
          const length = this.allSanxins.length;
          for (let i = 0; i < count; i++) {
            this.allSanxins.push({
              id: `sanxin${length + i}`,
              msg: `我是三心${length + i}号`,
              // 这里随便选一张图片就行
              src: require("../../src/asset/images/sanxin.jpg").default,
            });
          }
        },
        // 使用节流,提高性能
        doScroll: throttle(function () {
          // 监听可视区域的滚动事件
          // 公式:~~(滚动的距离 / 列表项 ),就能算出已经滚过了多少个列表项,也就能知道现在的startIndex是多少
          // 例如我滚动条滚过了160px,那么index就是1,因为此时第一个列表项已经被滚上去了,可视区域里的第一项的索引是1
          const index = ~~(this.$refs.scrollBox.scrollTop / this.itemHiehgt);
          if (index === this.startIndex) return;
          this.startIndex = index;
          if (this.startIndex + this.itemNum > this.allSanxins.length - 1) {
            this.getAllSanxin(30);
          }
        }, 200),
        getScrollBoxHeight() {
          // 获取可视区域的高度
          this.boxHeight = this.$refs.scrollBox.clientHeight;
        },
      },
      computed: {
        itemNum() {
          // 可视区域可展示多少个列表项? 计算公式:~~(可视化区域高度 / 列表项高度) + 2
          // ~~是向下取整的运算符,等同于Math.floor(),为什么要 +2 ,是因为可能最上面和最下面的元素都只展示一部分
          return ~~(this.boxHeight / this.itemHiehgt) + 2;
        },
        endIndex() {
          // endIndex的计算公式:(开始索引 + 可视区域可展示多少个列表项 * 2)
          // 比如可视区域可展示8个列表项,startIndex是0的话endIndex就是0 + 8 * 2 = 16,startIndex是1的话endIndex就是1 + 8 * 2 = 17,以此类推
          // 为什么要乘2呢,因为这样的话可以预加载出一页的数据,防止滚动过快,出现暂时白屏现象
          let index = this.startIndex + this.itemNum * 2;
          if (!this.allSanxins[index]) {
             // 到底的情况,比如startIndex是99995,那么endIndex本应该是99995 + 8 * 2 = 10011
            // 但是列表数据总数只有10000条,此时就需要让endIndex = (列表数据长度 - 1)
            index = this.allSanxins.length - 1;
          }
          return index;
        },
        tempSanxins() {
          //   可视区域展示的截取数据,使用了数组的slice方法,不改变原数组又能截取
          let startIndex = 0;
          if (this.startIndex <= this.itemNum) {
            startIndex = 0;
          } else {
            startIndex = this.startIndex + this.itemNum;
          }
          return this.allSanxins.slice(startIndex, this.endIndex + 1);
        },
        blankStyle() {
          // 上下方的空白处使用padding来充当
          let startIndex = 0;
          if (this.startIndex <= this.itemNum) {
            startIndex = 0;
          } else {
            startIndex = this.startIndex - this.itemNum;
          }
          return {
            // 上方空白的高度计算公式:(开始index * 列表项高度)
            // 比如你滚过了3个列表项,那么上方空白区高度就是3 * 150 = 450,这样才能假装10000个数据的滚动状态
            paddingTop: startIndex * this.itemHiehgt + "px",
             // 下方空白的高度计算公式:(总数据的个数 - 结束index - 1) * 列表项高度
            // 例如现在结束index是100,那么下方空白高度就是:(10000 - 100 - 1) * 150 = 1,484,850
            paddingBottom:
              (this.allSanxins.length - this.endIndex - 1) * this.itemHiehgt + "px",
              // 不要忘了加px哦
          };
        },
      },
    };
    </script>
    
    <style lang="scss" scoped>
    .v-scroll {
      height: 100%;
      /* padding-bottom: 500px; */
      overflow: auto;
    
      .scroll-item {
        height: 148px;
        /*  100%; */
        border: 1px solid black;
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 0 20px;
    
        img {
          height: 100%;
        }
      }
    }
    </style>
  • 相关阅读:
    POI使用详解
    POI导入导出Excel文件(二)
    jsp页面String转JSON
    jQuery的9中构造函数
    元素的BFC特性与自适应布局
    JS replace可以接受回调函数
    基于clip-path的任意元素的碎片拼接动效(源自鑫空间)
    解耦应用逻辑/事件处理程序
    CSS行高line-height的一些深入理解及应用
    常见的三种三栏网页宽度自适应布局 方法
  • 原文地址:https://www.cnblogs.com/songfengyang/p/16294275.html
Copyright © 2020-2023  润新知