如果后端一次性传给你 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
绝对定位,top
和left
都设为 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
添加滚动事件来动态改变要展示的数据,具体思路就是:
具体代码如下:
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>
看效果:
?
编辑文章到此就结束了。这只是一个简单的实现,还有很多可以优化的地方,例如滚动太快出现白屏的现象等,大家可以尝试一下,并试着优化一下。
希望本文能够对你有帮助。
地址:前端面试题库