En esta página

Transiciones y animaciones con Tailwind

12 min lectura TextoCap. 4 — Componentes y animación

Transiciones y animaciones con Tailwind

El movimiento es un componente esencial del diseño moderno. Las transiciones suaves y las animaciones bien ejecutadas hacen que una interfaz se sienta pulida y profesional. Tailwind proporciona utilidades para controlar transiciones CSS y animaciones predefinidas, además de herramientas para crear animaciones personalizadas con @theme.

Transiciones CSS

La clase `transition`

transition es la clase base que activa transiciones en las propiedades más comunes:

<!-- transition incluye: color, background-color, border-color,
     text-decoration-color, fill, stroke, opacity, box-shadow,
     transform, filter y backdrop-filter -->
<button class="bg-blue-600 hover:bg-blue-700 transition">
  Botón con transición
</button>

Variantes específicas de transición

<!-- Solo propiedad color (colores de texto, fondo, borde) -->
<button class="hover:bg-blue-700 hover:text-white transition-colors">...</button>

<!-- Solo opacity -->
<div class="opacity-0 hover:opacity-100 transition-opacity">...</div>

<!-- Solo transform (scale, rotate, translate) — más eficiente -->
<img class="hover:scale-110 transition-transform" src="img.jpg" alt="" />

<!-- Solo box-shadow -->
<div class="shadow-md hover:shadow-xl transition-shadow">...</div>

<!-- Todas las propiedades (costoso, úsalo con cuidado) -->
<div class="hover:bg-blue-500 hover:scale-105 hover:shadow-lg transition-all">...</div>

<!-- Personalizado: solo las propiedades que necesitas -->
<div class="transition-[transform,opacity] hover:scale-105 hover:opacity-100">...</div>

Duración de la transición

<div class="transition-colors duration-75">Muy rápida — 75ms</div>
<div class="transition-colors duration-100">100ms</div>
<div class="transition-colors duration-150">150ms (default)</div>
<div class="transition-colors duration-200">200ms</div>
<div class="transition-colors duration-300">300ms — suave</div>
<div class="transition-colors duration-500">500ms — lenta</div>
<div class="transition-colors duration-700">700ms — muy lenta</div>
<div class="transition-colors duration-1000">1000ms — 1 segundo</div>

Regla general: usa duration-150 o duration-200 para interacciones del usuario (hover, focus), y duration-300 o más para elementos que se muestran/ocultan.

Función de timing (easing)

<div class="transition ease-linear">  Velocidad constante</div>
<div class="transition ease-in">      Empieza lento, termina rápido</div>
<div class="transition ease-out">     Empieza rápido, termina lento</div>
<div class="transition ease-in-out">  Lento al inicio y al final</div>

Para entradas de elementos (appear), ease-out se siente más natural. Para salidas (disappear), ease-in. Para movimiento de objetos físicos, ease-in-out.

Delay de transición

<div class="transition-all duration-300 delay-0">Sin delay</div>
<div class="transition-all duration-300 delay-75">75ms de espera</div>
<div class="transition-all duration-300 delay-150">150ms de espera</div>
<div class="transition-all duration-300 delay-300">300ms de espera</div>
<div class="transition-all duration-300 delay-500">500ms de espera</div>

El delay es excelente para crear efectos de entrada escalonados:

<!-- Tarjetas que aparecen en cascada -->
<div class="flex flex-col gap-4">
  <div class="opacity-0 translate-y-4 animate-slide-up delay-0">Tarjeta 1</div>
  <div class="opacity-0 translate-y-4 animate-slide-up delay-100">Tarjeta 2</div>
  <div class="opacity-0 translate-y-4 animate-slide-up delay-200">Tarjeta 3</div>
</div>

Transformaciones CSS

Las transformaciones trabajan conjuntamente con las transiciones:

Translate (desplazamiento)

<!-- Desplazamiento en X -->
<div class="translate-x-0 hover:translate-x-1">→ Pequeño deslizamiento</div>
<div class="-translate-x-full">Completamente fuera por la izquierda</div>

<!-- Desplazamiento en Y -->
<div class="translate-y-0 hover:-translate-y-2">↑ Sube al hacer hover</div>
<div class="translate-y-full">Completamente fuera por abajo</div>

<!-- Drawer/panel deslizante -->
<div
  class="fixed right-0 top-0 h-full w-80 bg-white shadow-2xl z-50
         translate-x-full transition-transform duration-300
         data-[open=true]:translate-x-0"
  data-open="false"
>
  Panel lateral
</div>

Scale

<div class="scale-75">Reducido al 75%</div>
<div class="scale-100">Tamaño normal</div>
<div class="scale-110 hover:scale-125 transition-transform">Crece en hover</div>
<div class="scale-x-110">Solo eje X</div>
<div class="scale-y-90">Solo eje Y</div>

Rotate

<svg class="w-4 h-4 rotate-0 transition-transform
            group-open:rotate-180">▼</svg>

<!-- Patrón para accordions -->
<details class="group">
  <summary class="flex items-center justify-between cursor-pointer p-4">
    <span>¿Cuánto dura el curso?</span>
    <svg
      class="w-5 h-5 text-gray-400 rotate-0 group-open:rotate-180
             transition-transform duration-200"
      viewBox="0 0 20 20" fill="currentColor"
    >
      <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"/>
    </svg>
  </summary>
  <div class="px-4 pb-4 text-gray-600">
    El curso tiene una duración estimada de 12 horas.
  </div>
</details>

Animaciones predefinidas

Tailwind incluye 4 animaciones listas para usar:

animate-spin

<!-- Spinner de carga clásico -->
<div
  class="w-8 h-8 rounded-full border-4 border-gray-200 border-t-blue-600
         animate-spin"
  role="status"
  aria-label="Cargando..."
></div>

<!-- Spinner con texto -->
<div class="flex items-center gap-3">
  <div class="w-5 h-5 border-2 border-current border-t-transparent
              rounded-full animate-spin text-blue-600"></div>
  <span class="text-gray-600 text-sm">Cargando contenido...</span>
</div>

animate-ping

<!-- Indicador de estado en tiempo real -->
<div class="relative flex h-3 w-3">
  <div
    class="absolute inline-flex h-full w-full rounded-full
           bg-green-400 opacity-75 animate-ping"
  ></div>
  <div class="relative inline-flex h-3 w-3 rounded-full bg-green-500"></div>
</div>

<!-- Notificación en icono -->
<div class="relative inline-block">
  <svg class="w-6 h-6 text-gray-600"><!-- icono campana --></svg>
  <span class="absolute top-0 right-0 block h-2 w-2">
    <span class="animate-ping absolute inline-flex h-full w-full
                 rounded-full bg-red-400 opacity-75"></span>
    <span class="relative inline-flex rounded-full h-2 w-2 bg-red-500"></span>
  </span>
</div>

animate-pulse

<!-- Skeleton loader para tarjeta -->
<div class="bg-white rounded-xl p-6 shadow-sm space-y-4">
  <!-- Imagen skeleton -->
  <div class="bg-gray-200 rounded-lg h-40 animate-pulse"></div>

  <!-- Texto skeleton -->
  <div class="space-y-2">
    <div class="bg-gray-200 rounded h-4 w-3/4 animate-pulse"></div>
    <div class="bg-gray-200 rounded h-4 w-1/2 animate-pulse"></div>
    <div class="bg-gray-200 rounded h-4 w-5/6 animate-pulse"></div>
  </div>

  <!-- Botón skeleton -->
  <div class="bg-gray-200 rounded-lg h-10 w-32 animate-pulse"></div>
</div>

animate-bounce

<!-- Flecha de scroll down -->
<div class="flex justify-center pt-8 motion-safe:animate-bounce">
  <svg
    class="w-6 h-6 text-gray-400"
    fill="none" stroke="currentColor" viewBox="0 0 24 24"
  >
    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
          d="M19 9l-7 7-7-7"/>
  </svg>
</div>

Animaciones personalizadas con @theme

Para animaciones propias, define los @keyframes y agrégalos a @theme:

/* styles.css */
@import "tailwindcss";

/* Definir los keyframes */
@keyframes fade-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes fade-out {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(8px); }
}

@keyframes slide-in-right {
  from { opacity: 0; transform: translateX(24px); }
  to   { opacity: 1; transform: translateX(0); }
}

@keyframes scale-in {
  from { opacity: 0; transform: scale(0.95); }
  to   { opacity: 1; transform: scale(1); }
}

@keyframes shimmer {
  from { background-position: -200% 0; }
  to   { background-position: 200% 0; }
}

/* Registrar en @theme para generar clases animate-* */
@theme {
  --animate-fade-in:       fade-in 0.3s ease-out both;
  --animate-fade-out:      fade-out 0.2s ease-in both;
  --animate-slide-in-right: slide-in-right 0.3s ease-out both;
  --animate-scale-in:      scale-in 0.2s ease-out both;
  --animate-shimmer:       shimmer 2s linear infinite;
}
<!-- Uso de animaciones personalizadas -->
<div class="animate-fade-in">Aparece con fade</div>
<div class="animate-slide-in-right">Desliza desde la derecha</div>
<div class="animate-scale-in">Escala desde el centro</div>

<!-- Shimmer para skeletons más elegantes -->
<div
  class="animate-shimmer h-4 rounded
         bg-[linear-gradient(90deg,#e5e7eb_25%,#f3f4f6_50%,#e5e7eb_75%)]
         bg-[length:200%_100%]"
></div>

motion-safe y motion-reduce

Siempre es buena práctica envolver las animaciones con variantes de preferencia de movimiento:

<!-- Solo anima si el usuario NO tiene reducción de movimiento -->
<div class="motion-safe:animate-bounce motion-reduce:opacity-75">
  Indicador
</div>

<!-- Versión alternativa estática para motion-reduce -->
<button
  class="bg-blue-600 text-white px-4 py-2 rounded
         motion-safe:hover:scale-105 motion-safe:active:scale-95
         motion-safe:transition-transform
         motion-reduce:hover:bg-blue-700 motion-reduce:transition-colors"
>
  Botón accesible
</button>

Combinación de transición + transformación: botón avanzado

<button
  type="button"
  class="group relative overflow-hidden
         bg-gradient-to-r from-blue-500 to-blue-600
         text-white font-semibold px-8 py-4 rounded-2xl
         shadow-lg shadow-blue-500/30
         hover:shadow-xl hover:shadow-blue-500/40
         active:scale-95
         transition-all duration-200
         focus-visible:ring-2 focus-visible:ring-blue-400 focus-visible:ring-offset-2"
>
  <!-- Efecto shimmer en hover -->
  <span
    class="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent
           -translate-x-full group-hover:translate-x-full
           transition-transform duration-700"
  ></span>

  <!-- Texto del botón -->
  <span class="relative">Empezar ahora →</span>
</button>

Resumen

Para transiciones: usa transition-colors, transition-transform o transition-all según lo que necesites animar, con duration-150 a duration-300 para interacciones y ease-out para entradas. Para animaciones: las cuatro incluidas (spin, ping, pulse, bounce) cubren los casos más comunes. Para animaciones propias, define @keyframes y registra en @theme con --animate-*. Siempre incluye motion-reduce: para accesibilidad.

Prefiere transition-transform sobre transition-all
transition-all anima todas las propiedades modificables, lo que puede ser costoso para el rendimiento. Cuando solo necesitas animar movimiento, usa transition-transform. Para color, usa transition-colors. Ambas se pueden combinar: transition-[transform,colors].
Siempre incluye motion-reduce
Los usuarios con epilepsia o sensibilidad al movimiento pueden tener activada la opción 'reducir movimiento' en su OS. Siempre acompaña tus animaciones con motion-reduce:animate-none o motion-safe: para ser accesible.
<!-- transition aplica a color, background-color, border-color,
     text-decoration-color, fill, stroke, opacity,
     box-shadow, transform y filter -->
<button
  class="bg-blue-600 text-white px-6 py-3 rounded-xl font-semibold
         hover:bg-blue-700 hover:scale-105 hover:shadow-lg
         active:scale-95
         transition-all duration-200 ease-out"
>
  Botón con transición
</button>

<!-- Transición de solo transform (más eficiente) -->
<div class="hover:scale-110 transition-transform duration-300 ease-in-out">
  Escala suave
</div>

<!-- Transición de opacidad para fade -->
<div
  class="opacity-0 hover:opacity-100
         transition-opacity duration-500"
>
  Fade in
</div>

<!-- Transición con delay -->
<div
  class="translate-y-4 opacity-0
         group-hover:translate-y-0 group-hover:opacity-100
         transition-all duration-300 delay-150"
>
  Aparece con retraso
</div>