varHarrie / varharrie.github.io

:blue_book: Personal blog site based on github issues.
https://varharrie.github.io
MIT License
3.66k stars 544 forks source link

Vue实现虚拟列表(固定子项高度) #55

Open varHarrie opened 8 months ago

varHarrie commented 8 months ago
<template>
  <div class="list" ref="listRef" @scroll="onScroll">
   <div class="inner" :style="{ height: innerHeight }"></div>
   <div class="item" v-for="item of visibleList" :style="{ top: item.top }">{{ item.value }}</div>
  </div>
</template>

<script setup>
import { ref, reactive, computed, onMounted } from 'vue'

const itemHeight = 50;
const bufferSize = 2;
const list = Array.from({length: 10000}).map((_, i) => i);

const listRef = ref();
const listMeta = reactive({ offsetHeight: 0, scrollTop: 0 });

const onScroll = () => {
  listMeta.offsetHeight = listRef.value.offsetHeight;
  listMeta.scrollTop = listRef.value.scrollTop;
}

onMounted(() => {
  if (listRef.value) onScroll();
})

const innerHeight = computed(() => itemHeight * list.length + 'px');

const visibleList = computed(() => {
  const result = [];
  const { scrollTop, offsetHeight } = listMeta;

  const startIndex = Math.floor(scrollTop / itemHeight);
  const endIndex = Math.ceil((scrollTop + offsetHeight) / itemHeight);

  const finalStartIndex = Math.max(0, startIndex - bufferSize);
  const finalEndIndex = Math.min(list.length - 1,  endIndex + bufferSize);

  for (let i = finalStartIndex; i < finalEndIndex; i++) {
    result.push({
      top: i * itemHeight + 'px',
      value: list[i]
    })
  }

  return result;
});
</script>

<style>
.list {
  position: relative;
  height: 500px;
  width: 300px;
  border: 2px solid #000;
  overflow-y: scroll;

  .item {
    position: absolute;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    box-sizing: border-box;
    height: 50px;
    border: 1px solid #000;
  }
}
</style>