• 【项目小tips】Vue2中如何使用虚拟列表


    前言

    本系列主要整理前端项目中需要掌握的知识点。本节介绍Vue2中如何使用虚拟列表。


    一、使用场景

    在项目中,有些请求可能一次性返回上千条信息,这是如果使用v-for循环创建dom节点,创建上千条节点将消耗大量的性能,且不利于首屏加载。
    在这里插入图片描述

    二、虚拟列表

    以上的问题可以用虚拟列表来解决。虚拟列表的基本思路是,**在展示区只展示n个dom元素,而dom中展示哪部分内容则根据滚动条计算出来。**这样即使后端返回的数据再多,前端加载的dom元素也只有n个,利于首屏加载的同时也不会造成性能浪费。如图所示:
    在这里插入图片描述
    在图中,无论列表项内容到了多少项,DOM节点都只有10个,只不过是在不断更新而已。

    三、虚拟列表的手写实现

    • 思路与代码来自:凉爽爽爽爽爽爽爽爽爽的vue2 与 vue3 虚拟列表实现

    • 首先定义一个组件,用于虚拟列表的展示。传入的参数包括items(展示信息数组)、size(每条信息所占高度)、shownumber(每个可视区域展示的信息条数)

      <List :items="items" :size="60" :shownumber="10"/>
      
      • 1
    • 组件内部的盒子组成如下:外层是一个container、展示列表的盒子是list、list中用v-for遍历展示数据的数组showData,其中showData.length=shownumber。如下图所示:
      在这里插入图片描述

    • 并给container绑定滚动事件,当滚动事件发生后,计算滚动距离,用滚动距离除以每条数据的高度,即可得到被滚动条卷入的数据条数,因此showData数组的起始位置应该是this.start = Math.floor(scrollTop/this.size),终止位置为起始位置加上数组长度this.end = this.start+this.shownumber。

      computed: {
        // 最终筛选出的要展示的数据
        showData () {
          return this.items.slice(this.start, this.end)
        },
      }
      methods: {
        // 容器的滚动事件
        handleScroll () {
          // 获取容器顶部滚动的尺寸
          const scrollTop = this.$refs.container.scrollTop
          // 计算卷去的数据条数,用计算的结果作为获取数据的起始和结束下标
          // 起始的下标就是卷去的数据条数,向下取整
          this.start = Math.floor(scrollTop / this.size)
          // 结束的下标就是起始的下标加上要展示的数据条数
          this.end = this.start + this.shownumber
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18

      在这里插入图片描述

    • 还需要对list盒子进行定位,否则滚动条滚动后,dom数据虽然发生了变化,但是同时list盒子中需要展示的前几条数据也被滚动条圈进去了,如上图虚线中。所以就需要将container设置为相对定位,list设置为绝对定位,每次start值变了,便重新计算top的值。

      <div
        class="list"
        :style="{ top: listTop }"
      >
      <script>
      export default {
        computed: {
          // 列表向上滚动时要动态改变 top 值
          listTop () {
            return this.start * this.size + 'px'
          }
        }
      }
      </script>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      在这里插入图片描述

    • 完整代码如下:
      App.vue中

      
      <template>
        <div id="app">
          <List
            :items="items"
            :size="60"
            :shownumber="10"
          />
        div>
      template>
      
      <script>
      import List from './components/List.vue'
      export default {
        name: 'App',
        components: {
          List,
        },
        computed: {
          // 要进行渲染的数据列表
          items () {
            // 自己模拟一万条数据,将其内容进行填充
            return Array(10000).fill('').map((item, index) => ({
              id: index,
              content: '列表项内容' + index
            }))
          }
        }
      }
      script>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      style>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41

      List.vue中

      
      <template>
        <div
          class="container"
          :style="{ height: containerHeight }"
          @scroll="handleScroll"
          ref="container"
        >
          
          <div
            class="list"
            :style="{ top: listTop }"
          >
            
            <div
              v-for="item in showData"
              :key="item.id"
              :style="{ height: size + 'px' }"
            >
              {{ item.content }}
            div>
      
            
            <div
              class="bar"
              :style="{ height: barHeight }"
            />
          div>
        div>
      template>
      
      <script>
      export default {
        name: 'VircualList',
        props: {
          // 要渲染的数据
          items: {
            type: Array,
            required: true
          },
          // 每条数据渲染的节点的高度
          size: {
            type: Number,
            required: true
          },
          // 每次渲染的 DOM 节点个数
          shownumber: {
            type: Number,
            required: true
          }
        },
        data () {
          return {
            start: 0, // 要展示的数据的起始下标
            end: this.shownumber // 要展示的数据的结束下标
          }
        },
        computed: {
          // 最终筛选出的要展示的数据
          showData () {
            return this.items.slice(this.start, this.end)
          },
          // 容器的高度
          containerHeight () {
            return this.size * this.shownumber + 'px'
          },
          // 撑开容器内容高度的元素的高度
          barHeight () {
            return this.size * this.items.length + 'px'
          },
          // 列表向上滚动时要动态改变 top 值
          listTop () {
            return this.start * this.size + 'px'
          }
        },
        methods: {
          // 容器的滚动事件
          handleScroll () {
            // 获取容器顶部滚动的尺寸
            const scrollTop = this.$refs.container.scrollTop
      
            // 计算卷去的数据条数,用计算的结果作为获取数据的起始和结束下标
            // 起始的下标就是卷去的数据条数,向下取整
            this.start = Math.floor(scrollTop / this.size)
            // 结束的下标就是起始的下标加上要展示的数据条数
            this.end = this.start + this.shownumber
          }
        }
      }
      script>
      
      <style scoped>
      .container {
        position: relative;
        overflow-y: scroll;
        background-color: rgb(150, 195, 238);
        font-size: 20px;
        font-weight: bold;
        line-height: 60px;
        text-align: center;
      }
      
      .list {
        position: absolute;
        top: 0;
        width: 100%;
      }
      style>
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      • 96
      • 97
      • 98
      • 99
      • 100
      • 101
      • 102
      • 103
      • 104
      • 105
      • 106
      • 107
      • 108
      • 109

    四、 vue-virtual-scroll-list插件使用

    • vue-virtual-scroll-list插件可以实现虚拟列表。

    • 首先下载 vue-virtual-scroll-list插件并引入

      npm i vue-virtual-scroll-list
      import VirtualList from 'vue-virtual-scroll-list';
      export default {
        data(){
          return {
            VirtualList,
          }
        },
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • 使用virtualList标签

      <div style="height: 650px;">          //指定列表高度
        
      div>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • 构建新文件,设定列表内展示的结构

      <template>
          <div>
            {{ source.content }}
          div>
        template>
        
        <script>
          export default {
            name: 'item-component',
            props: {
              source: { 
                  type: Object,
                  default () {return {}}
              },
            },
          }
        script>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
    • 完整代码如下:
      VirtualList.vue中

      
      <template>
          <div style="height: 650px;">
            <virtual-list 
              style="height: 100%; overflow-y: auto;"
              :data-key="'id'"
              :data-sources="items"
              :data-component="itemComponent"
              :keeps="5"
              />
          div>
        template>
        
        <script>
        import VirtualList from 'vue-virtual-scroll-list';
        import itemComponent from './itemComponent';
        export default {
          data(){
            return {
              VirtualList,
              itemComponent,
              items:[],
            }
          },
          methods:{
              getData() { 
                  for(let i = 0; i < 10000; i++){
                      const obj = {id: i, content: `列表项内容${i}`};
                      this.items.push(obj) 
                  }
              }
          },
          mounted(){
              this.getData()
          }
      
        }
        script>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38

      itemComponent.vue中

      
      <template>
          <div>
            {{ source.content }}
            <hr/>
          div>
        template>
        
        <script>
          export default {
            name: 'item-component',
            props: {
              // index of current item
              source: { 
                  type: Object,
                  default () {
                  return {}
                  }
              },
            },
            mounted(){}
          }
        script>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23

      App.vue中

      
      <template>
        <div id="app">
          <VirtualList :items="items">VirtualList>
        div>
      template>
      
      <script>
      import VirtualList from './components/VirtualList.vue'
      export default {
        name: 'App',
        components: {
          VirtualList
        },
        computed: {
          // 要进行渲染的数据列表
          items () {
            // 自己模拟一万条数据,将其内容进行填充
            return Array(10000).fill('').map((item, index) => ({
              id: index,
              content: '列表项内容' + index
            }))
          }
        }
      }
      script>
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      style>
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
    • 最终效果:
      在这里插入图片描述

  • 相关阅读:
    zabbix集成openldap认证
    SolidWorks自定义装配体模板的方法
    [Java代码审计]—文件上传漏洞
    单例设计模式是什么?什么是 Singleton 单例设计模式?Python 单例(单件)设计模式示例代码
    队列【Java】
    快手版Sora「可灵」开放测试:生成超120s视频,更懂物理,复杂运动也能精准建模...
    R语言获取data.table分组下每个分组的第一条数据
    怎么辨别哪些才是真的低代码开发平台?
    Redis基础知识解答
    【FreeRADIUS】使用FreeRADIUS进行SSH身份验证
  • 原文地址:https://blog.csdn.net/weixin_44337386/article/details/126810986