Maquetación Web Dinámica / Animaciones CSS: de sencillas a profesionales
Loading states: spinners, skeletons y puntos que bailan
Toda app pasa tiempo esperando: a la red, al servidor, a un archivo. Ese tiempo de espera SE DISEÑA — la diferencia entre "se colgó" y "está trabajando" es una animación de carga bien hecha. Hoy construyes el trío clásico con todo lo aprendido: spinner, skeleton con shimmer y puntos que bailan. Tres patrones que copiarás a cada proyecto por el resto de tu carrera.
1. El spinner: un borde incompleto girando
La receta es un truco de percepción: un círculo con el borde pintado en 4 lados menos uno, girando en loop. El ojo ve "un arco persiguiéndose".
Dos decisiones con teoría detrás: la curva es linear — la excepción
legítima de la lección de easing: una rotación infinita con ease-in-out
"respira" a tirones, como un motor fallando. Y el @keyframes tiene UN
solo fotograma (to): cuando el from es el estado natural del elemento
(rotación 0), puedes omitirlo — el navegador lo completa.
2. Skeleton + shimmer: el esqueleto que brilla
Y aquí cobra la lección de rendimiento: el shimmer amateur anima el
background-position de un gradiente (paint en cada fotograma, el
escenario exacto del quiz de la lección 3). El profesional pinta el brillo
UNA vez — un pseudo-elemento con gradiente — y lo desliza con transform:
<div class="tarjeta-skeleton" aria-hidden="true">
<div class="hueso avatar"></div>
<div class="lineas">
<div class="hueso linea ancha"></div>
<div class="hueso linea"></div>
<div class="hueso linea corta"></div>
</div>
</div>
<p class="nota">El brillo es UN gradiente pintado una vez, deslizándose con translateX: cero repintado por fotograma.</p>body { font-family: system-ui; padding: 24px; background: #f8fafc; }
.tarjeta-skeleton { display: flex; gap: 14px; padding: 18px; background: white; border: 1px solid #e2e8f0; border-radius: 14px; max-width: 340px; }
.hueso { position: relative; overflow: hidden; background: #e2e8f0; border-radius: 6px; }
.hueso::after {
content: ""; position: absolute; inset: 0;
transform: translateX(-100%);
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.7), transparent);
animation: shimmer 1.4s ease-in-out infinite;
}
@keyframes shimmer { to { transform: translateX(100%); } }
.avatar { width: 52px; height: 52px; border-radius: 50%; flex-shrink: 0; }
.lineas { flex: 1; display: grid; gap: 10px; align-content: center; }
.linea { height: 12px; }
.ancha { width: 80%; }
.corta { width: 45%; }
@media (prefers-reduced-motion: reduce) { .hueso::after { animation: none; } }
.nota { font-size: 13px; color: #64748b; margin-top: 16px; max-width: 44ch; }La anatomía: cada "hueso" es un rectángulo gris con overflow: hidden, y
su ::after es una franja de luz (gradiente transparente → blanco →
transparente) que nace fuera del hueso (translateX(-100%)) y cruza hasta
el otro lado (translateX(100%)). Los fantasmas del módulo 9 + la regla
de oro de la lección 3, cocinando juntos. El aria-hidden="true" del
contenedor es cortesía: un lector de pantalla no necesita que le anuncien
rectángulos grises.
Tu skeleton tiene 8 huesos y en móvil el shimmer va a tirones. Un colega propone animar background-position en vez del pseudo-elemento, otro propone will-change en los 8 huesos. ¿Tú qué haces?
3. Los puntos que bailan (y el delay negativo)
El indicador de "escribiendo..." son tres copias de la MISMA animación
desfasadas — un stagger de la lección pasada, con un giro nuevo: el
delay negativo. Un delay de -160ms no espera: arranca la animación
como si ya llevara 160ms corriendo. Los tres puntos nacen bailando desde
el primer fotograma, cada uno en una fase distinta de la onda:
<div class="escribiendo" role="status" aria-label="Escribiendo">
<span class="punto"></span>
<span class="punto"></span>
<span class="punto"></span>
</div>
<p class="nota">Delays de -320ms, -160ms y 0: los tres nacen a mitad de onda, sin arranque en seco.</p>body { font-family: system-ui; padding: 28px; background: #f8fafc; }
.escribiendo { display: inline-flex; gap: 6px; padding: 14px 18px; background: white; border: 1px solid #e2e8f0; border-radius: 999px; }
.punto { width: 9px; height: 9px; border-radius: 50%; background: #6d28d9; animation: botar 1s ease-in-out infinite; }
.punto:nth-child(1) { animation-delay: -320ms; }
.punto:nth-child(2) { animation-delay: -160ms; }
@keyframes botar {
0%, 60%, 100% { transform: translateY(0); opacity: 0.45; }
30% { transform: translateY(-7px); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .punto { animation: none; opacity: 0.7; } }
.nota { font-size: 13px; color: #64748b; margin-top: 16px; }Fíjate en el detalle de reduced-motion de este patrón: no basta con apagar
la animación — los puntos quedarían con la opacidad del primer fotograma.
Se apaga Y se deja un estado estático digno (opacity: 0.7): el indicador
sigue COMUNICANDO "escribiendo", solo que quieto. Misma filosofía que el
flip del módulo 10: la preferencia apaga el movimiento, nunca la función.
Mini-reto
"El Observador" merece su estado de carga: construye la versión skeleton de una tarjeta de noticia (imagen, titular a dos líneas, metadato) con el shimmer de pseudo-elemento. Ponla junto a la tarjeta real y ajusta los huesos hasta que las siluetas COINCIDAN — un skeleton que no calca la estructura real produce un "salto" feo cuando llega el contenido.
Qué sigue
Todo lo que has animado corre contra el reloj. La próxima lección cambia el eje del tiempo por algo más interesante: EL SCROLL del usuario. Barras de progreso de lectura y elementos que se revelan al entrar en pantalla — sin una línea de JavaScript, con la API más nueva de todo el curso.