主要思路:
1.获取单个元素的高度,然后根据所有数据计算总高度,
2.通过滚动事件计算当前元素的索引,来给列表容器动态添加paddingTop
3.如果不设置paddingTop会怎么样:
因为虚拟列表是根据索引使用slice动态截取数据的,当滚动超过第一个元素时,就会触发slice更新数据。数据更新又会触发重新渲染,如果不加paddingTop的话,元素又会从顶部开始排列下来,这是scrollTop又会变为0。(这时慢慢滑的话会出现过了第一个元素又会从第一个元素开始,出现循环的现象。快速滑的则会出现白屏。)
vue3代码
import {
reactive,
ref,
computed,
onMounted,
watchEffect,
nextTick,
} from "vue";
const scrollBox = ref(null);
const items =ref(null);
const state = reactive({
DataList: [],
ItemBoxHeight: 0,
Itemnum: 1,
startIndex: 0,
});
const getdata = () => {
let list = [];
for (let i = 0; i < 100000; i++) {
list.push({
src: "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Flmg.jj20.com%2Fup%2Fallimg%2F1114%2F042421133312%2F210424133312-1-1200.jpg&refer=http%3A%2F%2Flmg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1669454206&t=bc5099c7f969e3b09cc744604316d025",
text: `我是${i}号`,
tid: i,
});
}
state.DataList = list;
};
onMounted(() => {
getdata();
});
const virtualList = computed(() => {
let endIndex = state.startIndex + state.Itemnum;
if (endIndex >= state.DataList.length) endIndex = state.DataList.length;
return state.DataList.slice(state.startIndex, endIndex);
});
const doscroll = () => {
const curScrollTop = scrollBox.value.scrollTop;
if (curScrollTop > state.ItemBoxHeight) {
const index = ~~(scrollBox.value.scrollTop / state.ItemBoxHeight);
items.value.style.setProperty(
"padding-top",
`${index * state.ItemBoxHeight}px`
);
state.startIndex = index;
} else {
items.value.style.setProperty("padding-top", "0px");
state.startIndex = 0;
}
};
watchEffect(() => {
if (state.DataList.length > 0) {
nextTick(() => {
// 计算每行高度
state.ItemBoxHeight = items.value.children[0].offsetHeight;
//计算屏幕内能显示的行数 +5是防止下拉过快出现白屏
state.Itemnum = ~~(scrollBox.value.clientHeight / state.ItemBoxHeight)+5;
// 设置列表总高度
const ListHeight = state.ItemBoxHeight * state.DataList.length;
items.value.style.setProperty("height", `${ListHeight}px`);
});
}
});
HTML代码
1. box-sizing: border-box(这个是重点)。因为padding的缘故。
<div ref="scrollBox" class="container" @scroll="doscroll">
<div ref="items" style="box-sizing: border-box">
<div class="item" v-for="item in virtualList" :key="item.tid">
<img :src="item.src" alt="" />
<span>{{ item.text }}</span>
</div>
</div>
</div>
CSS
.container {
height: 600px;
overflow-y: scroll;
}
.container .item {
height: 100px;
}
.container .item img {
width: 100px;
height: 100%;
}