Skip to content

长列表虚拟滚动

vue

vue
<template>
  <div class="virtual-list">
    <section class="container" ref="containerRef" @scroll="scrollView">
      <div class="scroll-bar" ref="scrollBarRef"></div>
      <ul ref="listRef" class="list">
        <li v-for="item in showList" :key="item">{{ item }}</li>
      </ul>
    </section>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted } from "vue";

const childrenHeight = 30;
const maxCount = 10;

const containerRef = ref<HTMLElement | null>(null);
const scrollBarRef = ref<HTMLElement | null>(null);
const listRef = ref<HTMLElement | null>(null);
const list = ref<number[]>([]);
const start = ref<number>(0);
const end = ref<number>(10);

const showList = computed(() => {
  return list.value.slice(start.value, end.value);
});

onMounted(() => {
  list.value = Array.from({ length: 100000 }, (_, v) => v);
  // 计算滚动容器高度
  containerRef.value!.style.height = `${childrenHeight * maxCount}px`;
  // 计算总的数据需要的高度,构造滚动条高度
  scrollBarRef.value!.style.height = `${childrenHeight * list.value.length}px`;
});

const scrollView = () => {
  let scrollTop = containerRef.value!.scrollTop;
  start.value = Math.floor(scrollTop / childrenHeight);
  end.value = start.value + maxCount;
  listRef.value!.style.top = `${start.value * childrenHeight}px`;
};
</script>

<style lang="css">
.virtual-list {
  text-align: center;
}

ul,
li {
  list-style: none;
}

.container {
  position: relative;
  overflow: scroll;
  border-bottom: 1px solid #ddd;
}

.list {
  position: absolute;
  width: 100%;
  top: 0;
  left: 0;
  text-align: left;
}

.list li {
  margin-bottom: 10px;
  line-height: 1.5;
  width: 100%;
  border-bottom: 1px dashed #1890ff;
  color: #52c41a;
  font-weight: bold;
}
</style>

react

jsx
import { useState, useRef, useMemo, useEffect } from "react"

export default function App() {
    const containerRef = useRef<HTMLElement>(null)
    const scrollBarRef = useRef<HTMLDivElement>(null)
    const listRef = useRef<HTMLListElement>(null)

    const childrenHeight = 30
    const maxCount = 10

    const [list, setList] = useState<number[]>([])
    const [start, setStart] = useState<number>(0)
    const [end, setEnd] = useState<number>(10)

    const sliceList = useMemo(() => {
        return list.slice(start, end)
    }, [list, start, end])

    useEffect(() => {
        setList(prev => {
            prev = Array.from({ length: 100000 }, (k, v) => v)
            containerRef.current!.style.height = `${childrenHeight * maxCount}px`
            scrollBarRef.current!.style.height = `${childrenHeight * prev.length}px`
            return prev
        })
    }, [])

    const scrollView = () => {
        let scrollTop = containerRef.current!.scrollTop
        const startIndex = Math.floor(scrollTop / childrenHeight)
        console.log(startIndex, "start===")
        setStart(startIndex)
        console.log(startIndex, "end===")
        const endIndex = startIndex + maxCount
        setEnd(endIndex)
        listRef.current!.style.top = `${start * childrenHeight}px`
    }

    return (
        <div className="App">
            <section className="container" ref={containerRef} onScroll={scrollView}>
                <div className="scroll-bar" ref={scrollBarRef}></div>
                <ul className="list" ref={listRef}>
                    {sliceList.map(item => (
                        <li key={item}>{item}</li>
                    ))}
                </ul>
            </section>
        </div>
    )
}