|
|
|
@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
|
|
<!-- 容器组件高度一定要是确定的高度,不能由内容撑起 -->
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
|
|
|
class="virtual-list-container"
|
|
|
|
|
|
|
|
ref="virtualListContainer"
|
|
|
|
|
|
|
|
@scroll="handleScroll($event)"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<!-- 绝对定位,当父组件overflow:auto时,占位组件可以撑起滚动条 -->
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
|
|
|
class="placeholder"
|
|
|
|
|
|
|
|
:style="{ height: `${totalHeight}px` }"
|
|
|
|
|
|
|
|
></div>
|
|
|
|
|
|
|
|
<!-- 偏移量设置在每个item的父容器上面 -->
|
|
|
|
|
|
|
|
<div :style="{ transform: getTransform }">
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
|
|
|
v-for="item in renderList"
|
|
|
|
|
|
|
|
:key="item.id"
|
|
|
|
|
|
|
|
class="item"
|
|
|
|
|
|
|
|
:style="{ height: `${itemHeight}px` }"
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
{{ item.text }}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
|
|
import { ref, computed, onMounted } from 'vue';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** 虚拟列表容器 */
|
|
|
|
|
|
|
|
const virtualListContainer = ref(null);
|
|
|
|
|
|
|
|
/** 虚拟列表容器高度 */
|
|
|
|
|
|
|
|
const containerHeight = ref(0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** 原始数据列表 */
|
|
|
|
|
|
|
|
const dataList = Array.from({ length: 1000 }, (_, i) => ({ id: i, text: `Item ${i}` }));
|
|
|
|
|
|
|
|
/** 虚拟列表单条item的高度 */
|
|
|
|
|
|
|
|
const itemHeight = 100;
|
|
|
|
|
|
|
|
/** 虚拟列表的总高度 */
|
|
|
|
|
|
|
|
const totalHeight = dataList.length * itemHeight;
|
|
|
|
|
|
|
|
const renderCount = computed(() => {
|
|
|
|
|
|
|
|
console.log('renderCount数量为:', Math.ceil(containerHeight.value / itemHeight));
|
|
|
|
|
|
|
|
return Math.ceil(containerHeight.value / itemHeight);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** 虚拟列表当前可见区域的起始索引 */
|
|
|
|
|
|
|
|
const start = ref(0);
|
|
|
|
|
|
|
|
/** 虚拟列表当前可见区域的结束索引 */
|
|
|
|
|
|
|
|
const end = computed(() => {
|
|
|
|
|
|
|
|
console.log('end索引为:', start.value + renderCount.value);
|
|
|
|
|
|
|
|
return start.value + renderCount.value;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
/** 虚拟列表当前可见区域的偏移量 */
|
|
|
|
|
|
|
|
const offset = ref(0);
|
|
|
|
|
|
|
|
const renderList = computed(() => {
|
|
|
|
|
|
|
|
console.log('renderList数据为:', dataList.slice(start.value, end.value + 1));
|
|
|
|
|
|
|
|
return dataList.slice(start.value, end.value + 1);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleScroll = (e) => {
|
|
|
|
|
|
|
|
const scrollTop = e.target.scrollTop;
|
|
|
|
|
|
|
|
start.value = Math.floor(scrollTop / itemHeight);
|
|
|
|
|
|
|
|
offset.value = scrollTop - (scrollTop % itemHeight);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const getTransform = computed(() => {
|
|
|
|
|
|
|
|
return `translate3d(0, ${offset.value}px, 0)`;
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
|
|
containerHeight.value = virtualListContainer.value.clientHeight;
|
|
|
|
|
|
|
|
console.log('containerHeight容器高度为:', containerHeight.value);
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
|
|
.virtual-list-container {
|
|
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
|
|
height: calc(100vh - 81px);
|
|
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
|
|
overflow: auto;
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.placeholder {
|
|
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
|
|
z-index: -1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.item {
|
|
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
|
|
color: #444444;
|
|
|
|
|
|
|
|
border-bottom: 1px solid #eee;
|
|
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
</style>
|