vue3实现不定高的虚拟列表

<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>

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值