深入解析vue3实现虚拟滚动

为什么使用虚拟列表

虚拟列表这种需求太常见了,可能每个项目组都做过这种事。。基本上只要数据稍微多一点(几千、上万的数据量),并且每一项有些复杂的dom结构,常规的列表就会出现明显的滚动卡顿,这时候就要用到“虚拟列表”,也可以叫“懒加载”,基本的做法就是只渲染可见范围内的列表项,一般也会在可见区域的上下加一些缓冲区,避免正常滚动的时候出现白屏。

虚拟列表原理

虚拟列表实际上就是使用少量的DOM节点显示长列表,即只创建并且显示我们视野中看到item节点,滚动过程中通过算法运算把视野中的节点更新成对应的节点。

功能实现思路

1、创建高度为200px的DOM容器

设置一个拥有10W条数据的列表,如果按原来方式就需要创建10W个dom节点,按每个item高度为40px计算,实际上每次出现在视野中的item为5个,剩余的9W多条元素实际上没有多大用处,并且加重了浏览器渲染的压力,但是如果我只创建视图显示的5条数据,则节省了9W多个节点的性能开销。

首先需要获取装载容器高度,需要在组件渲染之后,才能测量容器的真实高度。可以通过一个 ref 来绑定容器元素,在 nextTick 方法中获取容器高度

<script setup>
import {ref,nextTick,} from "vue";
const wrapper = ref(null);
let wrapperHeight = ref(0);
nextTick(() => {  
  wrapperHeight.value = wrapper.value.clientHeight;
});
</script>
<template> 
<div class="wrapper" ref="wrapper" > 
 ...  
</div>
</template>

获取了容器高度之后,计算视窗内应该显示的 DOM 数量

//真实DOM数 = Math.ceil(容器高度 / item高度)
let showItemNum = Math.ceil(wrapperHeight.value / 40)

效果如下:

10W的列表数据,只显示视野中的5条数据,符合预期。但是容器的滚动条(200/4刚好整除,连滚动条都没了)不是跟10W条数据应有的样子,因此需要想办法按10W数据的样子撑开容器。

2、撑开容器

为了让容器看起来跟真的拥有10W条数据一样,我们需要根据item条数和他的高度计算出对应的高度并且撑开容器。

<script setup>
...
//真实容器高度 = item总数 * 每条item的高度
let containerHeight = ref(arr.value.length * 40);
</script>
<template>  
<div class="wrapper" ref="wrapper" >      
  <div class="wrapper-scroll" :style="{ height: containerHeight + 'px' }" >                 
     ...     
  </div>    
</div>
</template>

效果如下:

这就得到10W条数据的列表了。

3、滚动列表实现

我们只需要根据在列表滚动到某位置的时候,去计算出当前的视窗中列表的索引,再根据索引对数据进行切片,从而将计算出来的数据片渲染到视图中。

运用computed方法,对数据进行切片处理

const showItem = computed(() => {  
  //为了让列表效果更好,我们将渲染的真实 DOM 数量多增加 3 个
  let showItemNum = Math.ceil(wrapperHeight.value / 40) +3;  
  return [...10W列表数据.slice(当前视野Item的索引, 视窗内应该显示的 DOM 数量)];
});

滚动计算当前状态的索引

const wrapperScroll = (e) => {  
  当前状态的索引 = Math.floor(当前滚动高度 / 每条item的高度);  
  
  //拿到了正确的数据,还需要计算出正确的位置
  数据片偏移位置 = 当前滚动高度
}};

具体源码如下:

<script setup>
import { ref, computed, nextTick } from "vue";
//设置10W条模拟数据
const count = ref(100000);let arr = ref([]);
for (let index = 0; index < count.value; index++) {  
   arr.value.push(index);
}

//容器真实高度
let containerHeight = ref(arr.value.length * 40);
//当前状态的索引
let startKey = ref(0);
//视窗内应该显示的 DOM 数量
let showItemNum = ref(0);
//容器dom节点
const wrapper = ref(null);
//容器高度
let wrapperHeight = ref(0);
nextTick(() => {  
   //获取容器高度
   wrapperHeight.value = wrapper.value.clientHeight; 
   //运算出应该显示的 DOM 数量
   showItemNum.value = Math.ceil(wrapperHeight.value / 40);
});
//片段容器偏移量
let scrollTopWrapper = ref(0);
//滚动事件
const wrapperScroll = (e) => {  
  //计算当前状态的索引
  let tempNum = Math.floor(e.target.scrollTop / 40); 
   
  //当前状态的索引发生变化才触发视图层刷新
  if (tempNum !== startKey.value) {    
    startKey.value = tempNum    
    scrollTopWrapper.value = e.target.scrollTop;  
  }
};
//对数据进行切片处理方法
const showItem = computed(() => {  
  
  return [...arr.value.slice(startKey.value, showItemNum.value + startKey.value + 3)];
});
</script>
<template> 
   <div class="wrapper" ref="wrapper" @scroll="wrapperScroll($event)">      
      <div class="wrapper-scroll" :style="{ height: containerHeight + 'px' }"   style="position: relative;" >        
        <div   :style="{ transform: `translateY(${scrollTopWrapper}px)` }"  style="position: absolute; width: 100%;"   >
           <div  v-for="(item, key) in showItem"   :key="key"  style="height:40px;line-height:40px"   >
           {{item}}
           </div>        
        </div>   
      </div>  
   </div>
</template>
<style>
.wrapper {  
   position: relative;  
   width: 200px;  
   height: 200px;  
   overflow: auto;  
   border: 1px solid #ccc;
}
</style>

具体效果如下:

文章来源:深入解析vue3实现虚拟滚动 | 猿小莫的博客

  • 7
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值