如果后端一次性傳給你 1 萬條數(shù)據(jù),該怎么辦,當然是讓他圓潤的走開,哈哈,開個玩笑。雖然這種情況很少,不過我在實際開發(fā)中還真遇到了類似的情況,接下來我將基于 vue3 實現(xiàn)一個簡單的虛擬滾動。
我們都知道,如果一次性展示所有的數(shù)據(jù),那么會造成頁面卡頓,虛擬滾動的原理就是將數(shù)據(jù)根據(jù)滾動條的位置進行動態(tài)截取,只渲染可視區(qū)域的數(shù)據(jù),這樣瀏覽器的性能就會大大提升,廢話不多說,我們開始。
具體實現(xiàn)
首先,我們先模擬 500 條數(shù)據(jù)
const data = new Array(500).fill(0).map((_, i) => i); // 模擬真實數(shù)據(jù)
然后準備以下幾個容器:
<template>
<div class="view-container">
<div class="content-container"></div>
<div class="item-container">
<div class="item"></div>
</div>
</div>
</template>
- view-container是展示數(shù)據(jù)的可視區(qū)域,即可滾動的區(qū)域
- content-container是用來撐起滾動條的區(qū)域,它的高度是實際的數(shù)據(jù)長度乘以每條數(shù)據(jù)的高度,它的作用只是用來撐起滾動條
- item-container是實際渲染數(shù)據(jù)的區(qū)域
- item則是具體渲染的數(shù)據(jù)
我們給這幾個容器一點樣式:
.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;
每條數(shù)據(jù)item給他一個20px的高度;
先把 500 條數(shù)據(jù)都渲染上去看看效果:

這里我們把高度都寫死了,元素的高度是實現(xiàn)虛擬滾動需要用到的變量,因此肯定不能寫死,我們可以用動態(tài)綁定style來給元素加上高度:
首先定義可視高度和每一條數(shù)據(jù)的高度:
const viewHeight = ref(400); // 可視容器高度
const itemHeight = ref(20); // 每一項的高度
用動態(tài)綁定樣式的方式給元素加上高度:
<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 使用每條數(shù)據(jù)的高度乘以數(shù)據(jù)總長度來得到實際高度。
然后我們定義一個數(shù)組來動態(tài)存放需要展示的數(shù)據(jù),初始展示前 20 條:
const showData = ref<number[]>([]); // 顯示的數(shù)據(jù)
showData.value = data.slice(0, 20); // 初始展示的數(shù)據(jù) (前20個)
showData里的數(shù)據(jù)才是我們要在item遍歷渲染的數(shù)據(jù):
<div
class="item"
:style="{
height: itemHeight + 'px',
}"
v-for="(item, index) in showData"
:key="index"
>
{{ item }}
</div>
接下來我們就可以給view-container添加滾動事件來動態(tài)改變要展示的數(shù)據(jù),具體思路就是:
- 根據(jù)滾動的高度除以每一條數(shù)據(jù)的高度得到起始索引
- 起始索引加上容器可以展示的條數(shù)得到結束索引
- 根據(jù)起始結束索引截取數(shù)據(jù)
具體代碼如下:
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;
// 根據(jù)初始索引和結束索引,截取數(shù)據(jù)
showData.value = data.slice(startIndex, endIndex);
console.log(showData.value);
};
打印一下數(shù)據(jù)看看數(shù)據(jù)有沒有改變:

可以看到數(shù)據(jù)是動態(tài)改變了,但是頁面上卻沒有按照截取的數(shù)據(jù)來展示,這是因為什么呢? 查看一下元素:

可以看到存放數(shù)據(jù)的元素 也就是 item-container 也跟著向上滾動了,所以我們不要讓它滾動,可以通過調整它的 translateY 的值來實現(xiàn),使其永遠向下偏移滾動條的高度:
<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>
看效果:

文章到此就結束了。這只是一個簡單的實現(xiàn),還有很多可以優(yōu)化的地方,例如滾動太快出現(xiàn)白屏的現(xiàn)象等,大家可以嘗試一下,并試著優(yōu)化一下。
希望本文能夠對你有幫助。
作者:路遙知碼li
鏈接:
https://juejin.cn/post/7301911743487590452