Skip to content
← Todas las preguntas
Avanzado

¿Cómo virtualizarías una lista de miles de elementos?

Rendimiento

La virtualización de listas renderiza solo los elementos visibles en el viewport en lugar de crear nodos del DOM para cada elemento. Una lista de 10.000 elementos con virtualización sigue usando alrededor de 20 nodos del DOM, igual que una lista de 100.

El problema

vue
<template>
  <!-- 10.000 componentes UserCard montados a la vez -->
  <div class="list">
    <UserCard v-for="user in users" :key="user.id" :user="user" />
  </div>
</template>

Cada nodo del DOM consume memoria, y montar 10.000 componentes bloquea el hilo principal. El navegador sufre o se bloquea.

Solución: vue-virtual-scroller

La opción más popular. RecycleScroller recicla los nodos del DOM a medida que el usuario se desplaza.

vue
<template>
  <RecycleScroller
    class="list"
    :items="users"
    :item-size="80"
    key-field="id"
    v-slot="{ item }"
  >
    <UserCard :user="item" />
  </RecycleScroller>
</template>

<script setup>
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
</script>

<style scoped>
.list {
  height: 600px; /* el contenedor debe tener altura fija */
}
</style>

Para elementos de altura variable, usa DynamicScroller:

vue
<template>
  <DynamicScroller :items="messages" :min-item-size="54" key-field="id">
    <template #default="{ item, index, active }">
      <DynamicScrollerItem :item="item" :active="active" :data-index="index">
        <ChatMessage :message="item" />
      </DynamicScrollerItem>
    </template>
  </DynamicScroller>
</template>

Alternativa: @tanstack/vue-virtual

Un virtualizador headless que te da control total sobre el renderizado. Sin estilos propios ni componente contenedor.

vue
<template>
  <div ref="parentRef" class="list-container">
    <div :style="{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }">
      <div
        v-for="row in virtualizer.getVirtualItems()"
        :key="row.key"
        :style="{
          position: 'absolute',
          top: 0,
          left: 0,
          width: '100%',
          height: `${row.size}px`,
          transform: `translateY(${row.start}px)`
        }"
      >
        <UserCard :user="users[row.index]" />
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useVirtualizer } from '@tanstack/vue-virtual'

const users = ref([/* miles de elementos */])
const parentRef = ref(null)

const virtualizer = useVirtualizer({
  count: users.value.length,
  getScrollElement: () => parentRef.value,
  estimateSize: () => 80,
  overscan: 5
})
</script>

<style scoped>
.list-container {
  height: 600px;
  overflow: auto;
}
</style>

Comparativa de librerías

LibreríaEnfoqueIdeal para
vue-virtual-scrollerBasado en componentes, todo incluidoConfiguración rápida, mayoría de casos
@tanstack/vue-virtualComposable headlessLayouts personalizados, control total
vue-virtual-scroll-gridVirtualización 2DLayouts de cuadrícula o galería

Cuándo NO virtualizar

  • Listas de menos de 50-100 elementos con contenido simple (el overhead no merece la pena)
  • Layouts de impresión donde todo el contenido debe renderizarse
  • Contenido crítico para SEO que necesita estar en el HTML inicial
  • Escenarios de accesibilidad donde todos los elementos deben ser alcanzables por lectores de pantalla a la vez

Publicado bajo la licencia MIT.