Marcio Cunha

Cómo renderizar listas largas en React manteniendo 60 FPS

Descubre cómo la virtualización de DOM resuelve bloqueos al mostrar miles de elementos en el navegador, manteniendo interfaces fluidas a 60 FPS.

Marcio Cunha•4 min
También disponible en:PortuguêsEnglish
Resumen
  • La virtualización de DOM dibuja en pantalla solo los elementos visibles en la ventana del navegador
  • El cálculo preciso de desplazamiento garantiza que la barra de barra nativa funcione sin saltos
  • Gestionar dinámicamente los eventos de escucha evita fugas de memoria en tablas y feeds extensos
  • Mantener el ritmo de renderizado en 60 fotogramas por segundo exige reutilizar nodos DOM
  • Optimizar estructuras de datos pesadas complementa el rendimiento obtenido con la virtualización

Por qué el navegador se bloquea al mostrar miles de elementos

Cuando una aplicación web intenta cargar diez mil registros de golpe en la pantalla, el motor del navegador sufre un impacto severo de rendimiento. El Modelo de Objetos del Documento, que es el árbol de elementos que el navegador monta en la memoria para mostrar la página, crece de forma desproporcionada y consume gigabytes de recursos. En la práctica, esto significa que ante cada clic o movimiento de desplazamiento, el sistema debe recalcular la posición de cada píxel, generando esos molestos tirones que arruinan la experiencia de usuario. Para profundizar en otros aspectos de optimización en interfaces modernas, puedes consultar nuestro artículo sobre cómo crear animaciones fluidas en React con Framer Motion, donde analizamos el flujo de renderizado.

El concepto de virtualización y el truco de la ventana deslizante

La virtualización de listas resuelve este problema limitando el esfuerzo del procesador exclusivamente a lo que el ojo humano realmente puede ver en ese milisegundo exacto. En lugar de inyectar diez mil filas en el código HTML, la aplicación crea un contenedor con la altura exacta que tendría toda la lista, pero dibuja internamente solo veinte o treinta elementos visibles. A medida que el usuario se desplaza hacia abajo, el componente recalcula qué datos deben aparecer y recicla los nodos visuales existentes. Esta ventana deslizante actualizada de forma dinámica engaña al cerebro del usuario, creando la ilusión de una lista infinita y pesada, mientras el hardware trabaja con una carga mínima y constante.

Calculando posiciones y manteniendo la barra de desplazamiento real

El mayor desafío técnico al implementar esta estrategia manualmente es lograr que la barra de desplazamiento lateral se comporte exactamente igual que si todos los elementos estuvieran presentes. Si la altura del contenedor principal refleja solo los veinte elementos visibles, la barra caerá al final con un leve toque. Para evitar esto, multiplicamos la cantidad total de elementos por la altura estimada de cada fila, aplicando este valor en un elemento espaciador invisible. Cuando se dispara el evento de desplazamiento, capturamos la posición superior exacta, la dividimos por la altura de la fila para hallar el índice inicial y aplicamos una transformación CSS para empujar los elementos visibles al lugar correcto.

Implementando un componente básico de virtualización

Para pasar a la práctica, vamos a estructurar un componente sencillo en React que calcula los elementos visibles basándose en la altura de la ventana de visualización. El código a continuación demuestra cómo interceptar el evento de desplazamiento y actualizar el estado de manera eficiente, asegurando que el navegador no gaste ciclos preciosos recalculando el diseño innecesariamente.

import React, { useState } from 'react';

export function VirtualList({ items, itemHeight, containerHeight }) {
  const [scrollTop, setScrollTop] = useState(0);
  const totalHeight = items.length * itemHeight;
  const startIndex = Math.floor(scrollTop / itemHeight);
  const visibleCount = Math.ceil(containerHeight / itemHeight);
  const endIndex = Math.min(startIndex + visibleCount + 1, items.length);
  const visibleItems = items.slice(startIndex, endIndex);

  return (
    <div
      onScroll={(e) => setScrollTop(e.currentTarget.scrollTop)}
      style={{ height: containerHeight, overflowY: 'auto', position: 'relative' }}
    >
      <div style={{ height: totalHeight, position: 'relative' }}>
        <div style={{ transform: `translateY(${startIndex * itemHeight}px)` }}>
          {visibleItems.map((item, index) => (
            <div key={startIndex + index} style={{ height: itemHeight }}>
              {item}
            </div>)
          )}
        </div>
      </div>
    </div>
  );
}

Errores comunes y el desafío de las alturas dinámicas

El código anterior funciona a la perfección cuando todos los elementos comparten exactamente la misma altura fija, pero el escenario cambia radicalmente al mostrar textos largos o contenidos de tamaño variable. Las alturas dinámicas exigen mediciones posteriores al renderizado, donde el componente ajusta de forma dinámica la posición del desplazamiento basándose en el tamaño real devuelto por el navegador. Ignorar este paso genera saltos abruptos en la barra lateral y una experiencia visual truncada que frustra a cualquier usuario que intente navegar rápidamente por la tabla. Para complementar esta implementación en entornos de producción, consulte también nuestros análisis técnicos sobre Cómo entrenar modelos de lenguaje con LoRA en datasets y Cómo implementar CQRS asíncrono en APIs de alto tráfico.

Consideraciones finales sobre el rendimiento de interfaces

Garantizar una tasa constante de sesenta fotogramas por segundo en aplicaciones web complejas exige disciplina arquitectónica y el uso correcto de las herramientas nativas del navegador. La virtualización de listas deja de ser un detalle técnico opcional y se convierte en un requisito obligatorio en paneles de control modernos, chats corporativos y fuentes de datos masivas. Al adoptar estas prácticas, proteges el dispositivo del cliente contra picos de consumo de memoria y aseguras una experiencia fluida, sin importar el volumen de datos procesados.