当前位置:   article > 正文

在Vue3中实现虚拟列表_vue3虚拟列表

vue3虚拟列表

目录

一、前言

二、代码实现

0.准备

1.计算

2.运行

三、完整代码

四、实现效果


一、前言

        当今的时代是大数据时代,往往一个列表就有成千上万条数据,而我们一一渲染的话,则需要耗费大量时间,导致网页打开缓慢。懒加载虽然减少了第一次渲染时间,加快了网页打开速度,但随着后续数据的不断载入拼接,列表的渲染时间也会越来越长。虚拟列表则很好的解决了这一问题。

        虚拟列表只渲染当前可视区域的列表,并不会将所有的数据渲染。以下以一个自制记账本(account book)为例。

二、代码实现

0.准备

        准备尽可能多的数据,比对使用虚拟列表前后渲染速率更加直观。

  1. //总数据 不需要响应式
  2. const accountData = []
  3. const getData = ()=>{
  4.    //两万条数据 测试直接渲染卡顿大概1s左右
  5. for(let i = 0; i < 10000; i++) {
  6.   accountData.push(
  7.           {
  8.                date: `2023-03-28`,
  9.                state: 0,
  10.                detail:`2月份工资`,
  11.                money: 1800
  12.           },
  13.           {
  14.                date: `2023-03-29`,
  15.                state: 1,
  16.                detail:'抽烟 喝酒 烫头',
  17.                money: 2000
  18.           })
  19.   }
  20. }

        需要准备内外两个列表容器,外部容器(account-list-outer)固定高度用于生成滚动条,内部容器(account-list-inner)用于撑开外部容器使得滚动条保持与未使用虚拟列表时一致。

  1. <template>
  2. <!-- 固定高度用于生成滚动条 -->
  3. <div class="account-list-outer" ref="outContainer">
  4. <!-- 用于撑开外部容器使得滚动条保持与未使用虚拟列表时一致 -->
  5.        <div class="account-list-inner">
  6.            <!-- 循环展示 -->
  7.   <div class="account-box" v-for="(item,index) in viewData" :key="index">
  8.               ...
  9.   </div>
  10.   </div>
  11.    </div>
  12. </template>
  13.    
  14. <script lang='ts' setup>
  15.    //外部容器dom元素
  16.    const outContainer = ref()
  17.    //内部容器padding-top
  18.    const paddingTop = ref('0px')
  19.    //内部容器padding-bottom
  20.    const paddingBottom = ref('0px')
  21.    //最终展示数据
  22.    const viewData = reactive([])
  23. </script>
  24.    
  25. <style scoped lang='scss'>
  26. .account-list-outer{
  27.  height: calc(100vh - 58px); //根据自身需求设定高度
  28.  overflow-y: scroll;
  29.  .account-list-inner{
  30.    padding-top: v-bind('paddingTop');
  31.    padding-bottom: v-bind('paddingBottom');
  32. }
  33. }
  34. </style>

1.计算

        在准备过程中已经获取了外部容器dom元素outContainer,定义好单行高度、外部容器高度以及滚动轴滚动长度。

  1. //单行高度
  2. const itemHeight = 70
  3. //外部容器高度
  4. const outContainerHeight = outContainer.value.cilentHeight
  5. //滚动轴滚动长度
  6. const scrollTop = outContainer.value.scrollTop

        通过这三个值可以计算出当前视口的起止下标。

  1. const startIndex = Math.floor(scrollTop / itemHeight)
  2. const endIndex = startIndex + Math.ceil(outContainerHeight / itemHeight)

        最终得出所需的外部容器padding以及当前可视的信息数据

  1. paddingTop.value = (startIndex * itemHeight).toString() + 'px'
  2. //accountData.length---总数据的长度
  3. paddingBottom.value = ((accountData.length - endIndex) * itemHeight).toString() + 'px'
  4. //清空viewData数据
  5. viewData.splice(0, viewData.length)
  6. //添加可视片段上的数据
  7. viewData.push(...accountData.slice(startIndex, endIndex + 1))

2.运行

  1. //需要获取dom元素 所以要在onMounted钩子中进行
  2. onMounted(async () => {
  3.  //获取原始数据(总数据)
  4.  getData()
  5.  //初始化创建虚拟列表
  6.  createVirtualList()
  7.  //添加事件监听
  8.  outContainer.value.addEventListener('scroll', createVirtualList)
  9. })

三、完整代码

  1. <template>
  2.    <div id="main-bg">
  3.      <div class="title">记账本</div>
  4.      <div class="account-list-outer" ref="outContainer">
  5.        <div class="account-list-inner">
  6.          <div class="account-box" v-for="(item,index) in viewData" :key="index">
  7.            <p class="date" :style="{backgroundColor:item.state?'#f2ddde':'#dff1d8', color:item.state?'#ae8286':'#8ea189'}">{{ item.date }}</p>
  8.            <div class="info">
  9.              <p class="detail">{{ item.detail }}</p>
  10.              <p class="state"><span v-if="item.state">支出</span><span v-else>收入</span></p>
  11.              <p class="money">{{item.money}}元</p>
  12.            </div>
  13.          </div>
  14.        </div>
  15.      </div>
  16.    </div>
  17. </template>
  18. <script lang='ts' setup>
  19. import { ref, reactive, onMounted } from 'vue'
  20.    
  21. interface AccountDataItem{
  22.  date: string //日期
  23.  state: number //收支状态 0为收入 1为支出
  24.  detail:string //详情
  25.  money: number //花费或收入
  26. }
  27. //原始数据
  28. const accountData: AccountDataItem[] = []
  29. // 最终展示数据
  30. const viewData: AccountDataItem[] = reactive([])
  31. // 外部容器dom元素
  32. const outContainer = ref()
  33. // 内部容器padding-top
  34. const paddingTop = ref('0px')
  35. // 内部容器padding-bottom
  36. const paddingBottom = ref('0px')
  37. //单行高度 可少不可多
  38. const itemHeight = 70
  39. //外部容器高度
  40. const outContainerHeight = outContainer.value.cilentHeight
  41. //滚动轴滚动长度
  42. const scrollTop = outContainer.value.scrollTop
  43. //获取原始数据
  44. const getData = ()=>{
  45.    //两万条数据 测试直接渲染卡顿大概1s左右
  46. for(let i = 0; i < 10000; i++) {
  47.   accountData.push(
  48.           {
  49.                date: `2023-03-28`,
  50.                state: 0,
  51.                detail:`2月份工资`,
  52.                money: 1800
  53.           },
  54.           {
  55.                date: `2023-03-29`,
  56.                state: 1,
  57.                detail:'抽烟 喝酒 烫头',
  58.                money: 2000
  59.           })
  60.   }
  61. }
  62. //创建虚拟列表
  63. const createVirtualList = () => {
  64.  const startIndex = Math.floor(scrollTop / itemHeight)
  65.  const endIndex = startIndex + Math.floor(outContainerHeight / itemHeight)
  66.  paddingTop.value = (startIndex * itemHeight).toString() + 'px'
  67.  // accountData.length---总数据的长度
  68.  paddingBottom.value = ((accountData.length - endIndex) * itemHeight).toString() + 'px'
  69.  // 清空viewData数据
  70.  viewData.splice(0, viewData.length)
  71.  // 添加可视片段上的数据
  72.  viewData.push(...accountData.slice(startIndex, endIndex + 1))
  73. }
  74. // 需要获取dom元素 所以要在onMounted钩子中进行
  75. onMounted(async () => {
  76.  // 获取原始数据(总数据)
  77.  await getData()
  78.  // 初始化创建虚拟列表
  79.  createVirtualList()
  80.  // 添加事件监听
  81.  outContainer.value.addEventListener('scroll', createVirtualList)
  82. })
  83. </script>
  84. <style scoped lang='scss'>
  85. p{
  86.  margin: 0;
  87.  padding: 0;
  88. }
  89. #main-bg{
  90.  width: 60%;
  91.  margin: 0 auto;
  92.  padding: 20px 10px 0 10px;
  93. }
  94. .title{
  95.  width: 100%;
  96.  text-align: left;
  97.  font-size: 24px;
  98.  font-family: cursive;
  99.  font-weight: 800;
  100.  padding-bottom: 10px;
  101.  border-bottom: 1px #EEEEEE solid;
  102. }
  103. .account-list-outer{
  104.  height: calc(100vh - 58px);
  105.  overflow-y: scroll;
  106.  .account-list-inner{
  107.    padding-top: v-bind('paddingTop');
  108.    padding-bottom: v-bind('paddingBottom');
  109.    .account-box{
  110.        margin: 10px 0;
  111.        border: 1px #EEEEEE solid;
  112.        border-radius: 8px;
  113.        .date{
  114.          font-size: 14px;
  115.          line-height: 14px;
  116.          text-align: left;
  117.          padding: 10px;
  118.       }
  119.        .info{
  120.          font-size: 15px;
  121.          line-height: 15px;
  122.          text-align: left;
  123.          padding: 10px 15px;
  124.          display: flex;
  125.          .detail{
  126.            width: 60%;
  127.         }
  128.          .state{
  129.            width: 10%;
  130.         }
  131.          .monry{
  132.            width: 30%;
  133.         }
  134.   }
  135. }
  136. }
  137. }
  138. </style>

四、实现效果

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小惠珠哦/article/detail/998140
推荐阅读
相关标签
  

闽ICP备14008679号