Tutoriales

Color en Animación Web: GSAP, Framer Motion y CSS

13 min de lectura

El color es la señal más rápida en el diseño en movimiento. Antes de que un usuario lea una etiqueta o siga una trayectoria de movimiento, ya ha registrado el cambio de color. Un botón que calienta de slate a ámbar al pasar el cursor, un anillo de estado que pulsa entre #10B981 y #059669 durante el procesamiento, una transición de página que barre un tono de marca por toda la ventana — estos no son decorativos. Comunican estado, urgencia, identidad de marca y estructura.

Pero implementar bien la animación de color en los ecosistemas de CSS, GSAP y Framer Motion requiere comprender cómo cada herramienta interpola el color, dónde están los cuellos de botella de rendimiento, y cómo los espacios de color modernos como OKLCH pueden marcar la diferencia entre puntos intermedios apagados y transiciones suaves y profesionales.

Esta guía cubre la pila completa de animación de color web, desde CSS puro hasta bibliotecas de animación JavaScript avanzadas, con patrones de código prácticos que puedes usar de inmediato.


Transiciones de Color en CSS: La Fundación

Las transiciones CSS son la vía de menor sobrecarga para la animación de color. No requieren JavaScript, no añaden peso al bundle y están aceleradas por GPU en navegadores modernos para la mayoría de los casos de uso.

Sintaxis Básica

.button {
  background-color: #3B82F6;
  transition: background-color 200ms ease-out;
}

.button:hover {
  background-color: #1D4ED8;
}

Esto realiza una transición de #3B82F6 (blue-500) a #1D4ED8 (blue-700) al pasar el cursor. La función de temporización ease-out — inicio rápido, final lento — coincide mejor con la percepción física del color que la lineal, haciendo que la transición se sienta natural y resuelta.

Qué Propiedades se Animan

Toda propiedad CSS que acepta un valor de color es animable: background-color, color, border-color, outline-color, box-shadow (el componente de color), text-decoration-color, caret-color, fill, stroke y stop-color (SVG). Las propiedades personalizadas registradas con @property y tipadas como <color> también son animables.

.card {
  background-color: #F8FAFC;
  border-color: #E2E8F0;
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.1);
  transition:
    background-color 300ms ease-out,
    border-color 300ms ease-out,
    box-shadow 300ms ease-out;
}

.card:hover {
  background-color: #EFF6FF;
  border-color: #93C5FD;
  box-shadow: 0 4px 12px rgb(59 130 246 / 0.15);
}

La Regla @property para Interpolación de Color Personalizada

CSS no puede interpolar nativamente entre valores de propiedades personalizadas a menos que el navegador conozca su tipo. La regla at @property registra una propiedad personalizada con sintaxis explícita, habilitando la animación suave:

@property --bg-color {
  syntax: "<color>";
  initial-value: #3B82F6;
  inherits: false;
}

.animated {
  background-color: var(--bg-color);
  transition: --bg-color 400ms ease;
}

.animated:hover {
  --bg-color: #7C3AED;
}

Con syntax: "<color>", el navegador sabe que debe interpolar los valores hex fotograma a fotograma en lugar de hacer un salto brusco. Esto habilita la animación de gradientes — algo no posible con CSS estándar:

@property --color-start {
  syntax: "<color>";
  initial-value: #3B82F6;
  inherits: false;
}

@property --color-end {
  syntax: "<color>";
  initial-value: #8B5CF6;
  inherits: false;
}

.gradient-button {
  background: linear-gradient(135deg, var(--color-start), var(--color-end));
  transition: --color-start 500ms ease, --color-end 500ms ease;
}

.gradient-button:hover {
  --color-start: #EF4444;
  --color-end: #F97316;
}

Animación de Color con Keyframes en CSS

La regla @keyframes permite secuencias de color de múltiples pasos que se ejecutan continuamente, se activan al cargar, o se disparan programáticamente mediante la alternancia de clases. A diferencia de las transiciones, los keyframes no requieren un evento de cambio de estado.

Indicadores de Estado en Bucle

@keyframes status-pulse {
  0%, 100% { background-color: #10B981; box-shadow: 0 0 0 0 rgb(16 185 129 / 0.4); }
  50%       { background-color: #34D399; box-shadow: 0 0 0 8px rgb(16 185 129 / 0); }
}

.status-dot {
  animation: status-pulse 2s ease-in-out infinite;
}

Esto crea un indicador de estado vivo y palpitante que pulsa de #10B981 a #34D399 mientras expande y desvanece simultáneamente una sombra de brillo — un patrón común en dashboards e interfaces de datos en vivo.

Rotación de Tono HSL con @property

Animar el canal de tono en HSL permite recorrer todo el espectro de color sin enumerar paradas individuales. La clave es registrar la variable de tono como tipo <number> para que el navegador la interpole numéricamente:

@property --hue {
  syntax: "<number>";
  initial-value: 220;
  inherits: false;
}

@keyframes hue-cycle {
  from { --hue: 0; }
  to   { --hue: 360; }
}

.rainbow-accent {
  background-color: hsl(var(--hue) 75% 55%);
  animation: hue-cycle 6s linear infinite;
}

Para animaciones de marca que no deben desviarse hacia tonos arbitrarios, limita el rango:

@keyframes brand-shift {
  0%   { --hue: 200; }   /* Cyan */
  50%  { --hue: 260; }   /* Morado */
  100% { --hue: 200; }
}

Animación de Color con GSAP

GSAP (GreenSock Animation Platform) es el estándar de la industria para animación compleja impulsada por JavaScript. Su manejo del color es automático y completo — pasa cualquier cadena de color hex, RGB, HSL o con nombre y GSAP interpola fotograma a fotograma.

Tweens Básicos de Color con GSAP

import { gsap } from "gsap";

// Anima el color de fondo de un elemento
gsap.to(".button", {
  backgroundColor: "#7C3AED",
  duration: 0.4,
  ease: "power2.out"
});

// Anima el color de texto con retraso
gsap.to(".heading", {
  color: "#F97316",
  duration: 0.6,
  delay: 0.2,
  ease: "expo.out"
});

GSAP analiza automáticamente el color calculado actual del elemento e interpola hasta el objetivo. Puedes usar cualquier cadena de color CSS válida como valor objetivo.

Timelines de GSAP para Secuencias de Color Coreografiadas

Los timelines permiten secuenciar múltiples cambios de color con relaciones de temporización precisas:

const tl = gsap.timeline({ paused: true });

tl.to(".hero-bg", { backgroundColor: "#1E3A8A", duration: 0.8 })
  .to(".hero-text", { color: "#BFDBFE", duration: 0.4 }, "-=0.4")
  .to(".cta-button", { backgroundColor: "#F59E0B", duration: 0.3 }, "-=0.2");

// Activar al desplazarse o por evento
document.querySelector(".trigger").addEventListener("mouseenter", () => tl.play());
document.querySelector(".trigger").addEventListener("mouseleave", () => tl.reverse());

El parámetro de posición ("-=0.4") superpone la segunda animación con los últimos 0.4 segundos de la primera, creando la sensación de capas del diseño de movimiento profesional en lugar de pasos mecánicos secuenciales.

Plugin GSAP ColorProps

El plugin GSAP ColorProps te permite animar propiedades CSS personalizadas que contienen valores de color:

import { gsap } from "gsap";

// Anima el valor de una propiedad CSS personalizada de color
gsap.to(document.documentElement, {
  "--brand-primary": "#EF4444",
  duration: 1,
  ease: "sine.inOut"
});

Esto es poderoso para animaciones de color a nivel de tema — animar una sola propiedad CSS personalizada propaga el cambio a través de cada elemento que la referencia.

GSAP con Colores SVG

GSAP maneja nativamente los atributos fill y stroke de SVG:

gsap.to(".logo-path", {
  fill: "#F97316",
  stroke: "#EA580C",
  strokeWidth: 2,
  duration: 0.5,
  ease: "power1.inOut"
});

Para íconos SVG complejos con múltiples paths que requieren diferentes objetivos de color, usa gsap.to() con un selector que coincida con múltiples elementos, o construye un timeline con objetivos individuales.

GSAP ScrollTrigger para Color Vinculado al Desplazamiento

Un patrón común en producción combina la animación de color con el progreso del desplazamiento:

import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";

gsap.registerPlugin(ScrollTrigger);

gsap.to(".site-header", {
  backgroundColor: "#0F172A",
  scrollTrigger: {
    trigger: ".hero",
    start: "top top",
    end: "bottom top",
    scrub: true   // Vincula el progreso de la animación directamente a la posición de desplazamiento
  }
});

La opción scrub: true vincula el progreso del tween a la posición de desplazamiento — a medida que el usuario se desplaza hacia abajo, el color de fondo del encabezado interpola desde su valor inicial hacia #0F172A. Esto crea el efecto de oscurecimiento del encabezado fijo común en sitios de marketing modernos.


Animación de Color con Framer Motion

Framer Motion es la biblioteca de animación dominante en el ecosistema de React. Su prop animate, y los hooks useMotionValue y useTransform proporcionan una interfaz declarativa para la animación de color.

Animación de Color Básica con animate

import { motion } from "framer-motion";

function ColorButton() {
  return (
    <motion.button
      initial={{ backgroundColor: "#3B82F6" }}
      whileHover={{ backgroundColor: "#1D4ED8" }}
      whileTap={{ backgroundColor: "#1E40AF" }}
      transition={{ duration: 0.2, ease: "easeOut" }}
    >
      Haz clic
    </motion.button>
  );
}

Framer Motion interpola entre valores hex automáticamente. Las variantes whileHover y whileTap vuelven a los valores initial cuando termina el evento.

Color Animado con variants

Las variantes habilitan estados de animación reutilizables con nombre en árboles de componentes complejos:

const cardVariants = {
  rest: { backgroundColor: "#F8FAFC", borderColor: "#E2E8F0" },
  hover: { backgroundColor: "#EFF6FF", borderColor: "#93C5FD" },
  selected: { backgroundColor: "#DBEAFE", borderColor: "#3B82F6" }
};

function SelectableCard({ isSelected }) {
  return (
    <motion.div
      variants={cardVariants}
      animate={isSelected ? "selected" : "rest"}
      whileHover={!isSelected ? "hover" : undefined}
      transition={{ duration: 0.25 }}
    >
      {/* contenido */}
    </motion.div>
  );
}

useMotionValue y useTransform para Color Reactivo al Cursor

useMotionValue crea un valor reactivo que no activa re-renders, lo que lo hace ideal para eventos de alta frecuencia como la posición del mouse:

import { motion, useMotionValue, useTransform } from "framer-motion";

function MagneticCard() {
  const x = useMotionValue(0);
  const y = useMotionValue(0);

  // Mapea la posición x (-150 a 150px) al tono (200 a 260)
  const hue = useTransform(x, [-150, 150], [200, 260]);
  const backgroundColor = useTransform(
    hue,
    (h) => `hsl(${h}deg 70% 55%)`
  );

  return (
    <motion.div
      style={{ backgroundColor, x, y }}
      onMouseMove={(e) => {
        const rect = e.currentTarget.getBoundingClientRect();
        x.set(e.clientX - rect.left - rect.width / 2);
        y.set(e.clientY - rect.top - rect.height / 2);
      }}
      onMouseLeave={() => {
        x.set(0);
        y.set(0);
      }}
    />
  );
}

El hook useTransform mapea el rango del valor de movimiento a una cadena de color, creando un cambio de tono suave que sigue el cursor. Esto se ejecuta completamente fuera del ciclo de renderizado de React — sin actualizaciones de estado, sin re-renders.

keyframes de Framer Motion para Animación de Color con Múltiples Paradas

<motion.div
  animate={{
    backgroundColor: ["#1E3A8A", "#7C3AED", "#DB2777", "#1E3A8A"]
  }}
  transition={{
    duration: 8,
    repeat: Infinity,
    ease: "linear"
  }}
/>

Pasar un array como valor de backgroundColor crea una secuencia de keyframes. Framer Motion recorre el array a intervalos de tiempo iguales. Añade un array times para controlar la distribución:

transition={{
  duration: 8,
  times: [0, 0.3, 0.7, 1],   // Permanece más tiempo cerca del primer color
  repeat: Infinity,
  ease: "easeInOut"
}}

Interpolación OKLCH para Animación de Color Suave

La mejora más importante disponible para la animación de color en 2024–2025 es cambiar la interpolación de sRGB a OKLCH. Tanto CSS como las bibliotecas JavaScript lo admiten, y la diferencia visual es dramática para ciertos pares de colores.

El Problema con la Interpolación sRGB

Cuando un navegador interpola entre dos colores en sRGB, mezcla linealmente los valores de los canales R, G y B. Esto es numéricamente simple pero perceptualmente no uniforme. Considera una transición de un naranja vívido #F97316 a un morado vívido #7C3AED: el punto medio en el espacio sRGB es un gris pardusco desaturado. Ese centro turbio es un subproducto del modelo de color, no la intención del diseño.

OKLCH (OK Luminosidad-Croma-Tono) está diseñado para que pasos numéricos iguales produzcan pasos percibidos iguales. El punto medio entre un naranja y un morado en el espacio OKLCH es un rojo vívido y saturado — porque la luminosidad y el croma permanecen constantes mientras solo cambia el tono.

OKLCH en Gradientes CSS

Usa la palabra clave in oklch para especificar el espacio de color de interpolación en los gradientes:

/* Interpolación sRGB — punto medio gris turbio */
background: linear-gradient(to right, #F97316, #7C3AED);

/* Interpolación OKLCH — punto medio rojo vívido */
background: linear-gradient(in oklch to right,
  oklch(65% 0.22 47deg),
  oklch(50% 0.22 295deg)
);

Para encontrar los equivalentes OKLCH de tus colores hex de marca, usa el Conversor de Color de ColorFYI para convertir entre formatos al instante.

También puedes controlar en qué dirección alrededor de la rueda de tonos viaja el gradiente:

/* Arco más corto (predeterminado) */
background: linear-gradient(in oklch to right,
  oklch(60% 0.2 60deg),   /* Amarillo */
  oklch(60% 0.2 300deg)   /* Magenta */
);

/* Arco más largo — pasa por azul-verde */
background: linear-gradient(in oklch longer hue to right,
  oklch(60% 0.2 60deg),
  oklch(60% 0.2 300deg)
);

Construye tu gradiente base con el Generador de Gradientes de ColorFYI, luego añade in oklch a la declaración del gradiente para una interpolación perceptualmente uniforme.

Animación OKLCH con @keyframes de CSS

@property --oklch-hue {
  syntax: "<number>";
  initial-value: 200;
  inherits: false;
}

@keyframes oklch-hue-cycle {
  from { --oklch-hue: 0; }
  to   { --oklch-hue: 360; }
}

.smooth-spectrum {
  /* Luminosidad (60%) y croma (0.2) constantes, solo varía el tono */
  background-color: oklch(60% 0.2 var(--oklch-hue));
  animation: oklch-hue-cycle 8s linear infinite;
}

Dado que la luminosidad y el croma OKLCH se mantienen constantes, esta animación mantiene un brillo y saturación percibidos consistentes a lo largo de toda la rotación de tono. Una rotación de tono HSL equivalente tendría parches notablemente más oscuros y más claros al pasar por las regiones de amarillo y violeta.

OKLCH en GSAP

GSAP acepta cadenas de color OKLCH en navegadores modernos:

gsap.to(".element", {
  color: "oklch(60% 0.2 260deg)",
  duration: 0.5,
  ease: "power2.out"
});

Para secuencias de keyframes con saturación percibida consistente:

gsap.to(".hero", {
  keyframes: [
    { backgroundColor: "oklch(55% 0.22 47deg)", duration: 1 },   // Naranja
    { backgroundColor: "oklch(55% 0.22 155deg)", duration: 1 },  // Verde
    { backgroundColor: "oklch(55% 0.22 260deg)", duration: 1 },  // Azul
    { backgroundColor: "oklch(55% 0.22 47deg)", duration: 1 },   // Vuelve a naranja
  ],
  repeat: -1,
  ease: "sine.inOut"
});

Los cuatro colores comparten 55% de luminosidad y 0.22 de croma — solo cambia el tono. El brillo y la saturación percibidos permanecen constantes durante todo el bucle.


Consejos de Rendimiento

La animación de color implica repintar píxeles. Comprender el modelo de coste ayuda a diseñar animaciones que permanecen suaves en dispositivos móviles de gama media.

El Pipeline de Renderizado

Tipo de Animación Etapa del Pipeline Coste
transform, opacity Compositor (GPU) Casi nulo
background-color, color, border-color Pintura (CPU + GPU) Bajo, repintado ocasional
width, height, margin Layout + Pintura Alto — evitar

La animación de propiedades de color activa un repintado en cada fotograma — el navegador recalcula los colores de los píxeles para el elemento afectado. Esto es generalmente rápido, pero el coste escala con el área que se está repintando.

Minimizar el Área de Repintado

Para animaciones de color en superficies grandes (fondos de ventana completa, secciones hero extensas), evita la animación directa de propiedades de color y en su lugar anima la opacity de una superposición posicionada:

.hero {
  position: relative;
  background-color: #1E3A8A;
}

.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  background-color: #7C3AED;
  opacity: 0;
  transition: opacity 800ms ease;
  pointer-events: none;
}

.hero.active::after {
  opacity: 1;
}

La técnica de superposición logra la apariencia de un cambio de color usando solo animación de opacity, que es solo compositor y no activa un repintado.

will-change para Animación Continua

Para elementos con animación siempre activa (indicadores de carga, efectos de fondo ambientales):

.always-animating {
  will-change: background-color;
}

Esto le indica al navegador que promueva el elemento a su propia capa de compositor. Úsalo con moderación — cada declaración will-change consume memoria GPU. Aplícalo solo a elementos que se animan continuamente, no a elementos que se animan ocasionalmente por interacción.

Respetar prefers-reduced-motion

La animación de color continua puede causar malestar vestibular a usuarios con sensibilidad al movimiento. Proporciona siempre una alternativa de movimiento reducido:

.pulse-animation {
  animation: status-pulse 2s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .pulse-animation {
    animation: none;
    background-color: #10B981; /* Color estático */
  }
}

En Framer Motion, el hook useReducedMotion proporciona acceso programático:

import { useReducedMotion } from "framer-motion";

function AnimatedBadge() {
  const shouldReduce = useReducedMotion();
  return (
    <motion.span
      animate={shouldReduce ? {} : { backgroundColor: ["#10B981", "#34D399", "#10B981"] }}
      transition={{ duration: 2, repeat: Infinity }}
    />
  );
}

gsap.matchMedia() de GSAP para Preferencias de Movimiento

const mm = gsap.matchMedia();

mm.add("(prefers-reduced-motion: no-preference)", () => {
  gsap.to(".hero-bg", {
    backgroundColor: "#7C3AED",
    scrollTrigger: { scrub: true }
  });
});

Elegir la Herramienta Correcta

Escenario Enfoque Recomendado
Cambio de color en estado hover/focus Transición CSS
Efecto ambiental en bucle continuo @keyframes CSS
Transformación de color vinculada al desplazamiento GSAP + ScrollTrigger
Secuencia coreografiada compleja Timeline GSAP
Color impulsado por estado de componente React Variantes de Framer Motion
Color reactivo al cursor/puntero useMotionValue de Framer Motion
Cambios de color en iconos SVG GSAP (maneja fill/stroke nativamente)
Animación de color en gradientes @property + CSS, o GSAP ColorProps

La regla es simple: usa CSS primero, añade GSAP cuando necesites control de timeline o integración con el desplazamiento, y usa Framer Motion cuando estés en React y quieras animación declarativa que co-exista con el estado del componente.

Para encontrar los valores exactos de hex, HSL y OKLCH para tus animaciones, el Conversor de Color de ColorFYI convierte entre todos los formatos de color principales. Para construir paradas de color de gradiente animadas, el Generador de Gradientes te permite visualizar el rango completo antes de comprometerte con el código.


Conclusiones Clave

  • Las transiciones CSS cubren la mayoría de las necesidades de animación de color de UI sin JavaScript y con excelente rendimiento del navegador.
  • Registra propiedades personalizadas como tipos <color> con @property para habilitar la interpolación suave de propiedades CSS personalizadas, incluyendo paradas de color en gradientes.
  • GSAP maneja cadenas de color nativamente (hex, RGB, HSL, OKLCH) y proporciona control preciso de timeline e integración con el desplazamiento que CSS no puede replicar.
  • El sistema de variantes y los hooks useMotionValue de Framer Motion son la solución más limpia para animación de color basada en React, especialmente para cambios de color impulsados por estado.
  • La interpolación OKLCH produce transiciones perceptualmente más suaves que sRGB porque mantiene luminosidad y saturación percibidas constantes — solo cambia el tono. Añade in oklch a las declaraciones de gradientes CSS y usa valores OKLCH en animaciones de keyframes para movimiento de color de calidad profesional.
  • La animación de color activa un repintado, no un cambio de layout. Minimiza el área de repintado usando técnicas de superposición basadas en opacity para superficies grandes.
  • Implementa siempre anulaciones de @media (prefers-reduced-motion: reduce) — es un requisito WCAG, no una cortesía opcional.

Colores relacionados

Marcas relacionadas

Herramientas relacionadas