<template>
<div class="container" ref="containerRef">
<div class="container-list" :style="srollStyle" ref="listRef">
<!-- id记一下 -->
<div class="container-list-item" v-for="(d,index) in renderList" :id="d.id" :key="index">{{ `item-${d.id}` }} {{ d.text }}</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted, nextTick, watch, onUnmounted} from 'vue'
const containerRef = ref(null)
const listRef = ref(null)
const dataSource = ref([
'123124324',
'666666666666666666666666666666666666666666999999999999999910000000000000000000000000000',
`12314
5768678`,
`111111111111111
222222222222222222
3333333333333`,
`999999999
10000000000,
111111111111111
1111111`,
`1233
245666666
98
78
78
78
89`,
`1233
245666666
98
78
78
78
89gggggggggggggggggggg`,
`1233
245666666
98
78
78
78
89ggggggggggggggggggggkkkkkkkkkkk`,
])
const position = ref([]) // 每一个数据的位置信息
const state = reactive({
viewHeight:0, //整个视高
listHeight:0,//列表高度
maxCount:0,// 容纳的最多的
startIndex:0,//开始的标签
estimatedHeight:50
})
const endIndex = computed(()=> Math.min(dataSource.value.length,state.startIndex + state.maxCount))
const renderList = computed(()=>dataSource.value.slice(state.startIndex,endIndex.value).map((m,i)=>({id:state.startIndex+i,text:m})))
//卷上去的高度,是计算后的startIndex的上一项的bottom的值
const offSetDis = computed(()=> state.startIndex > 0 ? position.value[state.startIndex-1].bottom : 0)
//计算样式
const srollStyle = computed(()=>{
return {
height:`${state.listHeight - offSetDis.value}px`,
transform:`translate3d(0, ${offSetDis.value}px, 0)`
}
})
const initPosition = ()=>{
const pos = []
for(let i = 0 ; i < dataSource.value.length;i++){
pos.push({
id:i,
height:state.estimatedHeight,
top:i*state.estimatedHeight,
bottom:(i+1)*state.estimatedHeight,
dHeight:0
})
}
position.value = pos
}
const setPosition = ()=>{
//获取listRef下的子元素
const nodes = listRef.value ? listRef.value.children : []
if(!nodes?.length) return;
const data = [...nodes]
data.forEach((d,i)=>{
const rect = d.getBoundingClientRect();
const dHeight = rect.height - state.estimatedHeight
position[i] = {
height:rect.height,
bottom:position.value[i].bottom - dHeight,
dHeight:dHeight
}
})
//计算整个list的高度
//从在屏幕的第一个算起,其他的就不用算了
const len = position.value.length
const startId = +nodes[0]?.id
//累计的高度差
const ljdhight = position.value[startId].dHeight
for(let i = startId + 1; i<len; i++){
const item = position.value[i]
item.top = position.value[i - 1].bottom
item.bottom = item.bottom - ljdhight
if(item.dHeight!==0){
ljdhight+=item.dHeight
item.dHeight = 0
}
}
state.listHeight = position.value[len - 1].bottom
}
//如何判断一个 item 滚出视图,只需要看它的 bottom <= scrollTop
//查找startIndex,二分查找
const getStartIndex = (list,value)=>{
//二分法
let left = 0;
let right = list.length - 1
let temp = -1
while(left < right){
const mid = Math.floor((left + right) / 2 )
if(value === list[mid].bottom){
return mid + 1
}else if(value > list[mid].bottom){
left = mid + 1
}else if(value < list[mid].bottom){
//找不到,取外面的那个,这个地方不能mid-1
if(temp === -1 || temp > mid) temp = mid
right = mid
}
}
return temp
}
const handleScroll = ()=>{
const { scrollTop } = containerRef.value
state.startIndex = getStartIndex(position.value,scrollTop)
console.log(state.startIndex)
}
watch(()=>state.startIndex,()=>{
console.log('start')
setPosition()
})
const init = ()=>{
state.viewHeight = containerRef.value ? containerRef.value.offsetHeight : 0
state.maxCount = Math.ceil(state.viewHeight / state.estimatedHeight) + 1
// 补充滚动事件绑定
containerRef.value && containerRef.value.addEventListener("scroll", handleScroll);
initPosition()
nextTick(() => {
setPosition();
});
}
const destroy = ()=>{
containerRef.value && containerRef.value.removeEventListener("scroll", handleScroll);
}
onMounted(()=>{
init()
})
onUnmounted(()=>{
destroy()
})
</script>
<style lang="scss" scoped>
.container {
width: 200px !important;
height: 200px;
background-color: aqua;
width: 100%;
overflow: auto;
&-list{
width: 100%;
background-color: red;
&-item{
width: 100%;
background-color: bisque;
}
}
}
</style>
vue3实现不定高的虚拟列表
最新推荐文章于 2024-07-26 11:41:15 发布