前端虚拟列表的实现(vue)

实现思路

  • 确定一个可视化的高度
  • 确定每个元素的高度
  • 由以上两点计算出可视化区域内元素的个数
  • 发生滚动的时候,滚动的距离可以确定可视化区域中第一个元素的index
  • 由以上三点可以确定滚动时,可视化区域中最后一个元素的Index
  • 把对应数据中这两个index之间的数据放进渲染列表中
<template>
  <div class="infinite-list-container" ref="list" @scroll="scrollEvent">
    <div class="infinite-list-phantom" :style="listHeight + 'px'">

    </div>
    <div class="infinite-list" :style="{transform: getTransform}">
      <div class="infinite-list-item" v-for="item in visibleData" :key="item.id" :style="{ height: itemSize + 'px', lineHeight: itemSize + 'px' }">
        <div class="left-section">
          {{item.title[0]}}
        </div>
        <div class="right-section">
          <div class="title">
            {{item.title}}
          </div>
          <div class="desc">
            {{item.content}}
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref, computed } from 'vue'
  interface Data {
    title: string;
    content: string;
    id: number | string
  }
  const itemSize = ref(100)
  const listData = ref<Data[]>([])
  const screenHeight: number = document.documentElement.clientHeight || document.body.clientHeight
  const visibleCount = ref(Math.ceil(screenHeight / itemSize.value))
  const startOffset = ref(0)
  const start = ref(0)
  const end = ref(start.value + visibleCount.value)
  const list = ref()
  let indexId = 0

  const listHeight =  computed(() => listData.value.length * itemSize.value)
  const getTransform = computed(() =>  `translate3d(0, ${startOffset}px, 0)`)
  const visibleData = computed(() => listData.value.slice(start.value, Math.min(end.value, listData.value.length)))

  const getTenListData = () => {
    if(listData.value.length > 200){
      return []
    }
    indexId++
    return new Array(10).fill({}).map((item, index) => ({ id: index + indexId, title: `${indexId}name${index}`, content: `${indexId}content${index}` }))
  }
  const scrollEvent = () => {
    const scrollTop = list.value.scrollTop
    start.value = Math.floor(scrollTop / itemSize.value)
    end.value = start.value + visibleCount.value
    if(end.value > listData.value.length){
      listData.value = listData.value.concat(getTenListData())
    }
    startOffset.value = scrollTop - (scrollTop % itemSize.value)
  }

  listData.value = getTenListData()
</script>

<style>
.scrollTopBtn {
    position: fixed;
    border-radius: 50%;
    font-size: 12px;
    color: white;
    background: goldenrod;
    bottom: 101px;
    right: 20px;
    z-index: 10000;
    width: 50px;
    height: 50px;
    text-align: center;
    line-height: 50px;

}

.infinite-list-container {
    margin-top: 10px;
    height: 99%;
    overflow: scroll;
    position: relative;
    -webkit-overflow-scrolling: touch;
}

.infinite-list-phantom {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    z-index: -1;
}

.infinite-list {
    left: 0;
    right: 0;
    top: 0;
    position: absolute;
    text-align: center;
}

.infinite-list-item {
    background: white;
    box-shadow: 0 0 10px rgba(144, 144, 144, 0.15);
    border-radius: 5px;

    display: flex;
    align-items: center;
    justify-content: center;
    /* padding: 10px; */
    margin-top: 10px;
}


.left-section {
    width: 25%;
    display: flex;
    justify-content: center;
    align-items: center;

    font-size: 25px;
    font-weight: bold;
    color: white;
    background: #6ab6fc;
    border-radius: 10px;
}

.right-section {
    height: 100%;
    margin-left: 20px;
    flex: 1;

}

.title {
    font-size: 14px;
    font-weight: 500;
    text-align: left;
    height: 14px;
}

.desc {
    margin-top: 10px;
    font-size: 12px;
    font-weight: 400;
    text-align: left;
    height: 12px;

}

</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值