注册
web

别再抱怨后端一次性传给你 1w 条数据了,几行代码教会你虚拟滚动!


如果后端一次性传给你 1 万条数据,该怎么办,当然是让他圆润的走开,哈哈,开个玩笑。虽然这种情况很少,不过我在实际开发中还真遇到了类似的情况,接下来我将基于 vue3 实现一个简单的虚拟滚动。



我们都知道,如果一次性展示所有的数据,那么会造成页面卡顿,虚拟滚动的原理就是将数据根据滚动条的位置进行动态截取,只渲染可视区域的数据,这样浏览器的性能就会大大提升,废话不多说,我们开始。


具体实现


首先,我们先模拟 500 条数据


const data = new Array(500).fill(0).map((_, i) => i); // 模拟真实数据

然后准备以下几个容器:


<template>
<div class="view-container">
<div class="content-container"></div>
<div class="item-container">
<div class="item"></div>
</div>
</div>
</template>


  • view-container是展示数据的可视区域,即可滚动的区域
  • content-container是用来撑起滚动条的区域,它的高度是实际的数据长度乘以每条数据的高度,它的作用只是用来撑起滚动条
  • item-container是实际渲染数据的区域
  • item则是具体渲染的数据

我们给这几个容器一点样式:


.view-container {
height: 400px;
width: 200px;
border: 1px solid red;
overflow-y: scroll;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}

.content-container {
height: 1000px;
}

.item-container {
position: absolute;
top: 0;
left: 0;
}

.item {
height: 20px;
}

view-container固定定位并居中,overflow-y设置为scroll


content-container先给它一个1000px的高度;


item-container绝对定位,topleft都设为 0;


每条数据item给他一个20px的高度;


先把 500 条数据都渲染上去看看效果:


初始渲染


这里我们把高度都写死了,元素的高度是实现虚拟滚动需要用到的变量,因此肯定不能写死,我们可以用动态绑定style来给元素加上高度:


首先定义可视高度和每一条数据的高度:


const viewHeight = ref(400); // 可视容器高度
const itemHeight = ref(20); // 每一项的高度

用动态绑定样式的方式给元素加上高度:


<div class="view-container" :style="{ height: viewHeight + 'px' }">
<div
class="content-container"
:style="{
height: itemHeight * data.length + 'px',
}"

>
</div>
<div class="item-container">
<div
class="item"
:style="{
height: itemHeight + 'px',
}"

>
</div>
</div>
</div>

content-container 使用每条数据的高度乘以数据总长度来得到实际高度。


然后我们定义一个数组来动态存放需要展示的数据,初始展示前 20 条:


const showData = ref<number[]>([]); // 显示的数据
showData.value = data.slice(0, 20); // 初始展示的数据 (前20个)

showData里的数据才是我们要在item遍历渲染的数据:


<div
class="item"
:style="{
height: itemHeight + 'px',
}"

v-for="(item, index) in showData"
:key="index"
>

{{ item }}
</div>

接下来我们就可以给view-container添加滚动事件来动态改变要展示的数据,具体思路就是:



  1. 根据滚动的高度除以每一条数据的高度得到起始索引
  2. 起始索引加上容器可以展示的条数得到结束索引
  3. 根据起始结束索引截取数据

具体代码如下:


const scrollTop = ref(0); // 初始滚动距离
// 滚动事件
const handleScroll = (e: Event) => {
// 获取滚动距离
scrollTop.value = (e.target as HTMLElement).scrollTop;
// 初始索引 = 滚动距离 / 每一项的高度
const startIndex = Math.round(scrollTop.value / itemHeight.value);
// 结束索引 = 初始索引 + 容器高度 / 每一项的高度
const endIndex = startIndex + viewHeight.value / itemHeight.value;
// 根据初始索引和结束索引,截取数据
showData.value = data.slice(startIndex, endIndex);

console.log(showData.value);
};

打印一下数据看看数据有没有改变:


滚动数据改变


可以看到数据是动态改变了,但是页面上却没有按照截取的数据来展示,这是因为什么呢? 查看一下元素:


问题


可以看到存放数据的元素 也就是 item-container 也跟着向上滚动了,所以我们不要让它滚动,可以通过调整它的 translateY 的值来实现,使其永远向下偏移滚动条的高度


<div
class="item-container"
:style="{
transform: 'translateY(' + scrollTop + 'px)',
}"

>

<div
class="item"
:style="{
height: itemHeight + 'px',
}"

v-for="(item, index) in showData"
:key="index"
>

{{ item }}
</div>
</div>

看效果:


效果


文章到此就结束了。这只是一个简单的实现,还有很多可以优化的地方,例如滚动太快出现白屏的现象等,大家可以尝试一下,并试着优化一下。


希望本文能够对你有帮助。


作者:路遥知码li
来源:juejin.cn/post/7301911743487590452

0 个评论

要回复文章请先登录注册