虚拟列表-渲染 10 万条数据

简介
虚拟列表是一种优化长列表渲染的技术,它可以在保持流畅性的同时,渲染大量的数据。

在传统的列表渲染中,如果列表非常长,会导致渲染时间过长,页面卡顿,用户体验变得非常差。而虚拟列表则是只渲染可见区域内的数据,而非全部渲染,这样就可以大大提高渲染效率,保持页面流畅性。

应用场景

虚拟列表技术在大数据量列表渲染场景中应用广泛,例如电商商品列表、社交动态列表等。

虚拟列表实现原理
<script setup lang="ts">
import axios from 'axios'
import { computed, onMounted, ref } from 'vue'

type Item = {
  id: number
  name: string
}

// 所有的数据,比如这个数组存放了十万条数据
const allListData = ref<Item[]>([])

const itemHeight = ref(40) // 每一条(项)的高度,比如 40 像素
const count = ref(10) // 一屏展示几条数据
const startIndex = ref(0) // 开始位置的索引
const endIndex = ref(10) // 结束位置的索引
const topVal = ref(0) // 父元素滚动位置

// 计算展示的列表
const showListData = computed(() => allListData.value.slice(startIndex.value, endIndex.value))

// 获取十万条数据
const getData = async () => {
  const res = await axios.get('http://localhost:3000/large-data')
  allListData.value = res.data.data
}

// 初始化加载
onMounted(() => {
  getData()
})

// 虚拟列表视口
const viewport = ref<HTMLDivElement>()

// 滚动这里可以加上节流,减少触发频次
const handleScroll = () => {
  // 非空判断
  if (!viewport.value) return
  // 获取滚动距离
  const scrollTop = viewport.value.scrollTop
  // 计算起始下标和结束下标,用于 computed 计算
  startIndex.value = Math.floor(scrollTop / itemHeight.value)
  endIndex.value = startIndex.value + count.value
  // 动态更改定位的 top 值,确保联动,动态展示相应内容
  topVal.value = viewport.value.scrollTop
}
</script>

<template>
  <h2>手写虚拟列表-原理{{ topVal }}</h2>
  <!-- 
    虚拟列表容器:类似“视口”,视口的高度取决于一次展示几条数据
    比如视口只能看到10条数据,一条40像素,10条400像素
    故,视口的高度为400像素,注意要开定位和滚动条 
  -->
  <div
    class="viewport"
    ref="viewport"
    @scroll="handleScroll"
    :style="{ height: itemHeight * count + 'px' }"
  >
    <!-- 占位 dom 元素,其高度为所有的数据的总高度 -->
    <div class="placeholder" :style="{ height: allListData.length * itemHeight + 'px' }"></div>
    <!-- 内容区,展示10条数据,注意其定位的top值是变化的 -->
    <div class="list" :style="{ top: topVal + 'px' }">
      <!-- 每一条(项)数据 -->
      <div
        v-for="item in showListData"
        :key="item.id"
        class="item"
        :style="{ height: itemHeight + 'px' }"
      >
        {{ item.name }}
      </div>
    </div>
  </div>
</template>

<style scoped lang="scss">
// 虚拟列表容器盒子
.viewport {
  box-sizing: border-box;
  width: 240px;
  border: solid 1px #000000;
  // 开启滚动条
  overflow-y: auto;
  // 开启相对定位
  position: relative;
  .list {
    width: 100%;
    height: auto;
    // 搭配使用绝对定位
    position: absolute;
    top: 0;
    left: 0;
    .item {
      box-sizing: border-box;
      width: 100%;
      height: 40px;
      line-height: 40px;
      text-align: center;
      // 隔行变色
      &:nth-child(even) {
        background: #c7edcc;
      }
      &:nth-child(odd) {
        background: pink;
      }
    }
  }
}
</style>
VueUse 方案
pnpm install @vueuse/core
<script setup lang="ts">
import { useVirtualList } from '@vueuse/core'
import axios from 'axios'
import { onMounted, ref } from 'vue'

type Item = {
  id: number
  name: string
}

// 所有的数据,比如这个数组存放了十万条数据
const allListData = ref<Item[]>([])

// 获取十万条数据
const getData = async () => {
  const res = await axios.get('http://localhost:3000/large-data')
  allListData.value = res.data.data
}

// 初始化加载
onMounted(() => {
  getData()
})

// 每一项的高度,比如 40 像素
const itemHeight = ref(40)

// vueuse方案:https://vueuse.org/core/useVirtualList/
const { list, containerProps, wrapperProps } = useVirtualList(allListData, {
  itemHeight: itemHeight.value,
})
</script>

<template>
  <h2>虚拟列表-VueUse实现</h2>
  <div v-bind="containerProps" class="viewport">
    <div v-bind="wrapperProps" class="list">
      <div v-for="item in list" :key="item.data.id" class="item">
        {{ item.data.name }}
      </div>
    </div>
  </div>
</template>

<style scoped lang="scss">
// 虚拟列表容器盒子
.viewport {
  box-sizing: border-box;
  width: 240px;
  height: 400px;
  border: solid 1px #000000;
  // 开启滚动条
  overflow-y: auto;
  // 开启相对定位
  position: relative;
  .list {
    width: 100%;
    height: auto;
    // 搭配使用绝对定位
    position: absolute;
    top: 0;
    left: 0;
    .item {
      box-sizing: border-box;
      width: 100%;
      height: 40px;
      display: flex;
      justify-content: center;
      align-items: center;
      // 隔行变色
      &:nth-child(even) {
        background: #c7edcc;
      }
      &:nth-child(odd) {
        background: pink;
      }
    }
  }
}
</style>
vue-virtual-scroller 方案
pnpm install vue-virtual-scroller@next
<script setup lang="ts">
import axios from 'axios'
import { onMounted, ref } from 'vue'

// https://github.com/Akryum/vue-virtual-scroller
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'

type Item = {
  id: number
  name: string
}

// 所有的数据,比如这个数组存放了十万条数据
const allListData = ref<Item[]>([])

// 获取十万条数据
const getData = async () => {
  const res = await axios.get('http://localhost:3000/large-data')
  allListData.value = res.data.data
}

// 初始化加载
onMounted(() => {
  getData()
})

// 每一项的高度,比如 40 像素
const itemHeight = ref(40)
</script>

<template>
  <h2>虚拟列表-vue-virtual-scroller实现</h2>
  <RecycleScroller
    class="viewport"
    :items="allListData"
    :item-size="itemHeight"
    key-field="id"
    v-slot="{ item }"
    item-class="item"
  >
    {{ item.name }}
  </RecycleScroller>
</template>

<style scoped lang="scss">
// 虚拟列表容器盒子
.viewport {
  box-sizing: border-box;
  width: 240px;
  height: 400px;
  border: solid 1px #000000;
  // 开启滚动条
  overflow-y: auto;
  // 开启相对定位
  position: relative;

  :deep(.item) {
    box-sizing: border-box;
    width: 100%;
    height: 40px;
    display: flex;
    justify-content: center;
    align-items: center;
    // 隔行变色
    &:nth-child(even) {
      background: #c7edcc;
    }
    &:nth-child(odd) {
      background: pink;
    }
  }
}
</style>
posted @   jialiangzai  阅读(338)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!

喜欢请打赏

扫描二维码打赏

微信打赏

点击右上角即可分享
微信分享提示