Skip to content

虚拟化列表

背景

使用列表或表格进行数据展示在前端中是很常见的事情,特别是在各种中后台管理系统和数据分析中更是常见。有时候,我们会遇到需要渲染大量数据的情况,但是表格在渲染大量数据的情况下很容易出现卡顿,通常我们可以使用分页、过滤的方式解决,但在某些业务中我们无法使用这些方法,例如,交易系统、教务系统等,在这些业务中的列表或表格一般是不能分页的。为了优化在列表中进行大量数据的渲染,我们需要使用一种被称为虚拟化列表的技术。

介绍

虚拟化列表:根据元素的滚动情况,仅在滚动容器元素的可视区域渲染一部分数据,以避免在渲染大量数据的情况下出现的卡顿。

实现

原理

列表项固定高度

0
1
2
3
4
5
6
7
8
9
10
11
12
vue
<template>
  <div
    ref="container"
    :style="{ height: height + 'px' }"
    class="border border-solid border-black overflow-auto rounded"
  >
    <div
      :style="{
        height: data.length * itemHeight + 'px',
        paddingTop: topPadding + 'px',
      }"
    >
      <div
        v-for="i in visibleData"
        :key="i"
        :style="{ height: itemHeight + 'px' }"
        :class="{
          'leading-50px': true,
          'text-center': true,
          'bg-gray-200': i % 2 === 0,
        }"
      >
        {{ i }}
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
const props = defineProps<{
  height: number
}>()

// 创建 0 到 9999 的数组
// new Array(10000).fill(0).map((_, i) => i)
// Array.from(Array(10000).keys())
// Array.from({ length: 10000 }, (_, i) => i)
const data = shallowRef(Array.from({ length: 10000 }, (_, i) => i))

const itemHeight = 50
const bufferSize = 3

// 计算可视区域内的元素个数
const visibleCount = computed(() => Math.ceil(props.height / itemHeight))
const start = ref(0)
const visibleData = computed(() =>
  data.value.slice(
    Math.max(0, start.value - bufferSize),
    start.value + visibleCount.value + bufferSize,
  ),
)

const topPadding = computed(
  () => Math.max(0, start.value - bufferSize) * itemHeight,
)
const container = shallowRef<HTMLDivElement | null>(null)

let _scrollTop = 0
// 缓存锚点元素的位置信息,默认为第一个元素
const anchor = {
  top: 0, // 锚点元素的顶部距离第一个元素的顶部的偏移量
  bottom: 50, // 锚点元素的底部距离第一个元素的顶部的偏移量
}

const updateAnchor = (scrollTop: number) => {
  start.value = Math.floor(scrollTop / itemHeight)
  anchor.top = start.value * itemHeight
  anchor.bottom = anchor.top + itemHeight
}

useEventListener(container, 'scroll', (e) => {
  const { scrollTop } = e.target as HTMLElement
  if (scrollTop < _scrollTop) {
    if (scrollTop < anchor.top) {
      updateAnchor(scrollTop)
    }
  } else {
    if (scrollTop > anchor.bottom) {
      updateAnchor(scrollTop)
    }
  }
  _scrollTop = scrollTop
})
</script>

列表项动态高度

#0 stillicidium brevis amo

Villa omnis ciminatio solutio. Spiculum cotidie stips sophismata vestigium. Artificiose vulnero consequuntur non colligo benigne consuasor conculco impedit demo. Reprehenderit est sordeo talis vulgaris ciminatio thymum magni dolore. Animadverto bene auxilium delinquo vestigium copiose cura xiphias.

#1 illum aveho contigo

Usque amaritudo dapifer vaco. Tempore nihil theca arbor adulescens deprimo. Depereo quo quisquam sursum aperiam voluptate aggero validus tui defendo. Subiungo venio utique advoco tergeo claudeo. Coadunatio stillicidium vinco adversus cultellus alveus dapifer sono delinquo sodalitas. Ad cultura varius stipes appello atque tardus suffoco.

#2 spargo acies defluo

Vobis sumptus venio facere comparo adimpleo vitiosus concido amiculum tristis. Conscendo abutor aptus casus sumo comminor. Aptus suscipit pecco coma suggero rerum aeneus vallum quisquam. Creptio angulus comes unde abbas turbo. Universe solvo temperantia sordeo quos. Tendo placeat tantillus appello solutio.

#3 valetudo argentum tabesco

Color claudeo cruciamentum vir territo nostrum supellex laudantium suasoria. Adduco traho ullus tantillus.

#4 tabgo socius defero

Adflicto alius deprecator alo asperiores libero curiositas. Degenero vereor abundans bos ago ventito similique tametsi aer catena. Corrupti basium clementia victoria laboriosam caelestis temeritas tepidus subnecto. Aperiam adopto thermae. Bestia comptus administratio dolor summopere. Cognatus advoco angulus.

#5 vesica velum sophismata

Voveo comparo ventus. Terror coma abutor abutor ceno commodi numquam vitiosus decor.

#6 solio supellex curtus

Decimus attonbitus timor pecus decor aestas. Ago bene nobis tubineus defaeco.

#7 crustulum cilicium defetiscor

Astrum sint laborum sit subvenio defluo cibo corrigo. Suppono alii labore cedo accusator.

#8 thymum suppono sit

Acervus cornu conqueror adopto. Coniuratio depereo demulceo atavus carus distinctio arceo ipsam vita. Coma ullam territo dolor.

#9 tubineus aliqua volubilis

Supra suadeo at verbum cervus spoliatio ancilla. Ars nam clementia expedita. Defessus cena supellex tego calculus vesica undique testimonium. Verumtamen facere tracto adeptio. Mollitia tabernus supplanto attollo celer auditor uter umquam spargo. Patruus tutis subnecto comptus et tolero decimus atqui.

#10 teneo aperio aperte

Comis soleo cinis crinis considero conforto. Virga censura creo curatio. Carus cattus verbum deserunt tepesco. Tabesco talus optio testimonium arma error. Sodalitas vehemens capio animus nobis communis conor suggero. Ancilla incidunt verto brevis supellex repellat usitas aranea.

#11 quae theatrum alienus

Placeat solitudo carcer abbas. Alienus utpote defessus utrum aestivus caelestis aqua sit cumque vox. Compello aurum velum. Tenus terror uxor tametsi carpo depraedor antepono spectaculum vester ago. Arca addo bibo umquam arx varius calcar subvenio nisi. Claudeo substantia utique solitudo vix solus carcer trado aestus confido.

#12 arx crinis articulus

Contego texo ullam comprehendo ademptio conturbo admitto. Temptatio clementia accusamus deleo cenaculum nisi molestias. Confido vinum repellendus allatus tonsor absconditus utrum turpis. Deleo villa suggero volaticus corpus venustas considero.

缺点

虽然虚拟化表格解决了超大数据渲染的性能问题,但它并不是完美无瑕的。该方案有以下两个缺点:

  • 网络响应的负载增加
  • 为了保存大量数据,使用的内存容量增加

即使虚拟化的表格是高效的,但是当数据负载过大时,网络内存容量也会成为您应用程序的瓶颈。 因此请牢记,虚拟化表格永远不是最完美的解决方案,请考虑数据分页、过滤器等优化方案。

其它工具和库