Skip to content

Loaders y spinners ​

Cuando algo tarda, el usuario necesita saber que la página no se ha congelado. Un buen indicador de carga reduce la ansiedad y hace que la espera se sienta más corta. Aquí tienes 15 variantes en CSS puro.

Concepto: animaciones infinitas y animation-delay negativo ​

Los loaders se basan en tres ideas:

  1. Bucles infinitos: animation-iteration-count: infinite.
  2. Curva linear para giros: si un spinner usa ease, acelera y frena en cada vuelta y parece "cojear".
  3. Desfase entre piezas con animation-delay. Un truco profesional es usar retrasos negativos: animation-delay: -.4s hace que la animación empiece como si ya llevara 0.4 s en marcha, así todas las piezas se mueven desde el primer fotograma.
css
.spinner {
  width: 48px; height: 48px; border-radius: 50%;
  border: 5px solid rgb(139 92 246 / .2);  /* aro tenue */
  border-top-color: #8b5cf6;               /* un solo lado de color */
  animation: girar 1s linear infinite;
}
@keyframes girar { to { transform: rotate(360deg); } }

12 loaders CSS ​

Spinner, anillo doble, puntos, "escribiendo…", barras, pulso, órbita, flip, cónico, cuadrícula, reloj de arena y latido. Todos se personalizan con las variables --color, --color-2, --tam y --vel.

12 loaders en CSS puro

Algunas técnicas destacadas:

  • Anillo doble: ::before y ::after giran en sentidos opuestos con animation-direction: reverse.
  • Barras: se anima transform: scaleY(), nunca height (más fluido).
  • Cónico: un conic-gradient recortado con mask: radial-gradient(...) para dejar sólo un aro con degradado.
  • Cuadrícula: cada celda recibe un --d (diagonal) y el retraso es calc(var(--d) * .1s), creando una ola en diagonal.
  • Latido: un cuadrado rotado 45° más dos círculos (::before y ::after) forman el corazón.

Accesibilidad en loaders

  • Añade role="status" y aria-label="Cargando" para que los lectores de pantalla anuncien el estado.
  • Con prefers-reduced-motion, en lugar de congelar el loader (perdería su significado), lo hacemos mucho más lento: :root { --vel: 3s; }.

Skeleton y barras de progreso ​

El skeleton (esqueleto) muestra la forma del contenido antes de que llegue. Plataformas como YouTube, LinkedIn o Facebook lo usan porque se percibe más rápido que un spinner. Las barras de progreso son ideales cuando sabes cuánto falta (subidas de archivos, pasos de un formulario).

Skeleton, progreso e indeterminada

Skeleton con brillo (shimmer):

css
.sk {
  background: linear-gradient(90deg, #e2e8f0 25%, #f8fafc 50%, #e2e8f0 75%);
  background-size: 200% 100%;
  animation: shimmer 1.4s linear infinite;
  border-radius: 6px;
}
@keyframes shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }

Barra de progreso accesible: usa role="progressbar" con aria-valuenow, aria-valuemin y aria-valuemax, y actualiza el valor desde JS. También existe el elemento nativo <progress>, más sencillo pero más difícil de estilizar.

¿Spinner, skeleton o barra?

SituaciónMejor opción
Espera corta (menos de 1 s)Nada, o un spinner pequeño en el botón
Carga de una página o listaSkeleton
Sabes el porcentaje (subida, instalación)Barra determinada
No sabes cuánto faltaBarra indeterminada o spinner

Mostrar y ocultar un loader en un proyecto real ​

js
const loader = document.querySelector('.spinner');
const contenido = document.querySelector('#contenido');

async function cargarProductos() {
  loader.hidden = false;                       // mostrar
  try {
    const res = await fetch('/api/productos');
    const datos = await res.json();
    contenido.innerHTML = datos.map((p) => `<li>${p.nombre}</li>`).join('');
  } catch (err) {
    contenido.textContent = 'No se pudieron cargar los productos.';
  } finally {
    loader.hidden = true;                      // ocultar siempre, haya error o no
  }
}

Error común: loaders que parpadean

Si la petición tarda 100 ms, el loader aparece y desaparece en un parpadeo molesto. Muestra el loader sólo si la carga supera ~300 ms con un setTimeout que cancelas al terminar.

Ejercicios ​

  1. Crea un spinner de 3 aros concéntricos que giren a distintas velocidades.
  2. Convierte el loader de "puntos" en un indicador de "escribiendo…" dentro de una burbuja de chat.
  3. Crea un skeleton para una tarjeta de producto (imagen, título, precio y botón).
Ver solución del ejercicio 2
html
<div class="burbuja"><div class="escalar"><i></i><i></i><i></i></div></div>
css
.burbuja {
  display: inline-flex; padding: 12px 16px;
  background: #e2e8f0; border-radius: 18px 18px 18px 4px;
}
.escalar i { width: 8px; height: 8px; background: #64748b; }

Resumen ​

  • Giros: linear + infinite. Pasos de varias piezas: animation-delay (también negativo).
  • Anima transform (rotate, scale), no width/height.
  • Skeleton = degradado + background-position animado.
  • role="status" / role="progressbar" para accesibilidad; con reduced-motion, ralentiza en vez de eliminar.

Academia Web — aprende, copia y construye.