Loading skeletons con CSS y keyframes

Resumen

Aprende a implementar loading skeletons en una aplicación web usando solo HTML y CSS, una técnica que mejora la percepción de velocidad y mantiene al usuario informado sobre qué contenido está por aparecer mientras carga la API.

Por qué usar loading skeletons en lugar de un spinner

Un rectángulo gris animado que imita la forma del contenido final comunica mucho más que un círculo girando. El usuario ya intuye qué va a aparecer en cada lugar y la espera se vuelve tolerable, no frustrante.

En una conexión rápida, el efecto apenas se percibe: un grisecito por una fracción de segundo. Pero al simular una conexión lenta desde las DevTools del navegador, los skeletons aparecen claramente con una animación suave de opacidad mientras el HTML se renderiza y la API responde.

¿Qué es un loading skeleton? Es un placeholder visual con la forma del contenido final, normalmente un rectángulo gris con animación sutil, que se muestra mientras los datos cargan desde una API.

Cómo se estructura el HTML para mostrar el estado de carga

La idea es dejar la estructura del contenedor lista, pero sin contenido real. Dentro de la sección Trending Preview Movie List se triplica un div con dos clases combinadas: movie-container y movie-container--loading. Aunque no haya imagen dentro, esa segunda clase activa la animación que emula la pieza de contenido por venir.

Lo mismo aplica para las categorías: se triplica el contenedor con category-container--loading y se comenta el h3 del título, porque aún no se conoce el nombre de la categoría. Solo queda un rectángulo animado.

Esta misma técnica se replica en tres zonas del proyecto [04:30]:

  • La sección de Trending Preview con películas destacadas.
  • Las categorías genéricas que vienen del API.
  • La Generic List vertical que aparece al filtrar por una categoría como Aventuras.

Cómo se construye la animación con CSS y keyframes

El CSS hace todo el trabajo visual. La clase movie-container--loading recibe un color de fondo gris claro definido por una variable, un border-radius de 8 píxeles y el mismo tamaño exacto que tendrán las imágenes finales: 225 por 150 píxeles. Mantener las dimensiones idénticas evita que el scroll salte cuando la información real reemplace al skeleton.

También se añade un margin-bottom para que los elementos no queden pegados, y se aplica una animación llamada loading-skeleton.

Qué hace exactamente el keyframe de la animación

El @keyframes es minimalista pero efectivo [07:45]:

  • En 0% la opacidad está en 100%.
  • En 50% la opacidad baja a 0%.
  • En 100% vuelve a 100%.

Esto crea un parpadeo suave que dura un segundo en bucle. No es divertido, pero comunica que algo está pasando y que la app no se quedó congelada.

¿Para qué sirve animation-delay en los skeletons? Sirve para que los elementos no parpadeen al mismo tiempo. Aplicando un retraso al segundo y tercer contenedor, se crea una sensación de carga progresiva más natural.

Cómo se diferencian las animaciones por sección

El category-container--loading usa la misma lógica con un tamaño distinto. Aquí los delays se reparten distinto: todos los de la columna izquierda parpadean a la vez y los de la derecha un instante después. Cargar uno por uno se veía raro en piezas pequeñas, mientras que en las películas grandes el efecto secuencial sí funciona.

Cómo desaparecen los skeletons cuando llega la información

Esta parte no requirió tocar JavaScript, y ahí está lo elegante de la solución. La función createMovies ya estaba escrita para limpiar el contenedor antes de inyectar las películas reales. Esto se hace con container.innerHTML = '', que vacía el HTML inicial, incluyendo los divs con la clase --loading.

El flujo queda así:

  1. El HTML inicial pinta los rectángulos grises con la animación.
  2. La app dispara los eventos de carga del DOM o cambio de hash en navigation.js.
  3. Cuando la API responde, createMovies o createCategories limpia el contenedor.
  4. Se itera el array y se insertan los elementos reales con su contenido.

La misma lógica funciona para películas similares, que vienen de un endpoint distinto, y para todas las vistas de la aplicación. Un solo patrón, replicado consistentemente.

Buenas prácticas que cumple esta solución

La propuesta es sencilla, pero cumple con varios principios de UX y rendimiento que vale la pena destacar:

  • Mantiene las dimensiones exactas del contenido final para evitar saltos de layout.
  • Comunica visualmente qué tipo de contenido vendrá en cada espacio.
  • No sobrecarga la app con animaciones complejas ni librerías externas.
  • Funciona automáticamente porque el JavaScript existente ya limpiaba los contenedores antes de inyectar datos.

La próxima optimización va por otro lado: dejar de cargar todas las imágenes apenas entras a la app y cargar solo las que el usuario alcanza a ver, aplicando lazy loading con scroll. Cuéntame en los comentarios qué estrategia usaste tú para resolver tus pantallas de carga.