Ir al contenido
charmotion

Cómo agregar animación a una app de React o Next.js

Por el equipo de Charmotion · Actualizado el

Ilustración de portada de la guía Cómo agregar animación a una app de React o Next.js, con personajes de demostración de Charmotion

Para agregar animaciones en React, instala motion e importa motion desde "motion/react" para las transiciones de componentes; usa CSS para efectos simples, DotLottieReact para archivos de Lottie y useRive para un personaje interactivo. En Next.js, cualquier componente que se anime en el navegador debe ser un client component.

Puntos clave

  • Framer Motion ahora se llama Motion: npm install motion y luego importa desde "motion/react".
  • AnimatePresence permite que los elementos se animen al salir antes de que React los elimine.
  • <MotionConfig reducedMotion="user"> respeta la preferencia de movimiento reducido del visitante.
  • Usa @lottiefiles/dotlottie-react para Lottie y useRive para personajes controlados por estados.
  • En el App Router, empieza con "use client" los componentes que animan.

¿Ya tienes un personaje o logo? Mira tu personaje en pantallas reales de una app: pantallas de splash, onboarding, carga y estado vacío. Es gratis, funciona en tu navegador y no sube nada.

¿Framer Motion ahora se llama Motion?

Sí. La librería que antes se publicaba como framer-motion ahora es Motion, un proyecto independiente. Su guía de actualización es breve: desinstala el paquete anterior, instala motion y cambia la ruta de importación.

npm uninstall framer-motion
npm install motion
// before
import { motion } from "framer-motion";
// after
import { motion } from "motion/react";

La API de componentes es la misma, así que el código existente con motion.div sigue funcionando después de cambiar la importación. Los tutoriales escritos antes del cambio de nombre siguen siendo válidos; solo lee framer-motion como motion/react.

Motion es una buena opción por defecto para animar interfaces en React: elementos que entran y salen, cambios de layout, gestos y transiciones de elementos compartidos. No es la herramienta adecuada para una ilustración dibujada o una mascota de marca, que llegan como archivo desde una herramienta de diseño. Para eso, usa Lottie o Rive, que se explican más abajo.

Tabla de decisión: APIs integradas para el feedback de la interfaz, efectos de íconos para íconos, una máquina de estados de Rive para personajes y video para clips de marketing
Elige el método de animación según la tarea.

¿Cómo se anima un componente con Motion?

Reemplaza un elemento por su equivalente de motion y describe los estados inicial y final. Envuelve los elementos condicionales en AnimatePresence para que puedan reproducir una animación de salida antes de desmontarse.

import { AnimatePresence, MotionConfig, motion } from "motion/react";

export function Toast({ open, message }: { open: boolean; message: string }) {
  return (
    <MotionConfig reducedMotion="user">
      <AnimatePresence>
        {open && (
          <motion.div
            key="toast"
            role="status"
            initial={{ opacity: 0, y: 12 }}
            animate={{ opacity: 1, y: 0 }}
            exit={{ opacity: 0, y: 12 }}
            transition={{ duration: 0.2 }}
          >
            {message}
          </motion.div>
        )}
      </AnimatePresence>
    </MotionConfig>
  );
}

Cada hijo de AnimatePresence necesita una key estable. Con reducedMotion="user", Motion desactiva las animaciones de transformación y de layout cuando el visitante tiene activado el movimiento reducido, mientras que los cambios de opacidad y color siguen ejecutándose. Normalmente ese es el comportamiento que quieres. Para un control más fino, el hook useReducedMotion() devuelve un booleano con el que puedes decidir qué hacer. Coloca MotionConfig una sola vez cerca de la raíz de tu app, en lugar de en cada componente.

¿Cuándo basta con CSS en React?

Con frecuencia. Los efectos de hover, los anillos de foco, los spinners y los fundidos simples no necesitan una librería. Un CSS module o una clase de Tailwind con una transición no cuesta nada de JavaScript y funciona incluso antes de que React hidrate la página.

/* Button.module.css */
.button { transition: transform 150ms ease-out; }
.button:active { transform: scale(0.97); }

@media (prefers-reduced-motion: reduce) {
  .button { transition: none; }
}

Recurre a Motion cuando CSS se vuelva incómodo:

  • un elemento debe animarse al salir antes de que React lo elimine;
  • un cambio de layout (reordenar una lista, expandir una tarjeta) debe animarse con suavidad;
  • los valores vienen del estado, de gestos o de la posición del scroll;
  • varias animaciones deben ejecutarse en secuencia.

Una división sensata es CSS para las microinteracciones, Motion para la coreografía de componentes y un archivo de animación para todo lo que haya dibujado un ilustrador. Mantener las tres cosas separadas también mantiene pequeño tu bundle: las páginas que solo usan CSS nunca cargan Motion.

¿Cómo se agrega un archivo de Lottie en React?

Instala el reproductor para React de LottieFiles y renderízalo con la ruta a un archivo .lottie o .json:

npm install @lottiefiles/dotlottie-react
import { DotLottieReact } from "@lottiefiles/dotlottie-react";

export function EmptyState() {
  return (
    <DotLottieReact
      src="/animations/empty-inbox.lottie"
      loop
      autoplay
      style={{ width: 240, height: 240 }}
    />
  );
}

DotLottieReact renderiza un canvas y acepta props de canvas como style y className. Para controles de reproducción personalizados, su prop dotLottieRefCallback te da la instancia del reproductor subyacente, que tiene métodos para reproducir, pausar y buscar. El componente proviene del mismo proyecto dotlottie-web que el reproductor web simple, así que los archivos se comportan igual en React y en páginas estáticas.

Lottie funciona bien para ilustraciones que se reproducen de principio a fin: estados vacíos, escenas de onboarding, confeti. Si la animación necesita reaccionar al estado de la app, por ejemplo pasar de carga a éxito, considera en su lugar un archivo construido en torno a una máquina de estados (state machine).

¿Cómo se agrega un personaje animado con estados?

Usa el runtime de Rive para React. useRive carga el archivo y devuelve un RiveComponent para renderizar; con autoBind: true, los hooks del view model te dan setters para las propiedades del archivo. Rive recomienda @rive-app/react-webgl2 para la mayoría de los proyectos, y @rive-app/react-canvas tiene la misma API.

import { useEffect } from "react";
import {
  useRive,
  useViewModelInstanceNumber,
  useViewModelInstanceTrigger,
} from "@rive-app/react-canvas";

type Status = "idle" | "loading" | "done";

export function Mascot({ status }: { status: Status }) {
  const { rive, RiveComponent } = useRive({
    src: "/mascot.riv",
    stateMachine: "CharacterViewModelMachine",
    autoplay: true,
    autoBind: true,
  });
  const vmi = rive?.viewModelInstance;
  const { setValue: setMood } = useViewModelInstanceNumber("mood", vmi);
  const { trigger: success } = useViewModelInstanceTrigger("success", vmi);

  useEffect(() => {
    if (status === "loading") setMood?.(5);
    if (status === "done") { success?.(); setMood?.(0); }
  }, [status, setMood, success]);

  return (
    <div style={{ width: 200, height: 200 }}>
      <RiveComponent aria-label="Assistant mascot" />
    </div>
  );
}
loading: mood 5, se repite en loop mientras el trabajo está en curso.

Controla el personaje desde props, nunca durante el render, y define el tamaño del contenedor en lugar del canvas. Charmotion genera este tipo de archivo a partir de un personaje, una mascota de marca o un logo que subas, junto con código de React listo para usar; la guía de Rive en React explica a fondo la API basada en inputs y todas las opciones de paquetes.

¿Cómo evitas problemas de SSR y de hidratación en Next.js?

En el App Router, los componentes son server components por defecto, y los server components no pueden usar hooks, APIs del navegador ni manejadores de eventos. Cualquier archivo que llame a useRive, renderice DotLottieReact o use Motion con estado debe empezar con la directiva "use client":

"use client";

import { Mascot } from "./Mascot";
export default Mascot;

Mantén pequeña la frontera del cliente. Haz que el widget animado sea un client component e impórtalo en una página que, por lo demás, se renderiza en el servidor, para que solo ese widget envíe JavaScript.

Si una librería accede a window al importarse y sigue fallando durante el prerenderizado, cárgala solo en el navegador con next/dynamic:

"use client";
import dynamic from "next/dynamic";

const Mascot = dynamic(() => import("./Mascot").then((m) => m.Mascot), {
  ssr: false,
  loading: () => <div style={{ width: 200, height: 200 }} />,
});

El placeholder evita saltos de diseño (layout shift) mientras carga el runtime. En el Pages Router se aplica el mismo patrón dynamic(..., { ssr: false }), sin la directiva.

Cómo agregar un personaje animado a una página de Next.js

  1. Instala el runtime. Ejecuta npm install @rive-app/react-canvas (o @rive-app/react-webgl2) y coloca el .riv en public/.
  2. Crea un client component. Empieza el archivo con "use client" y llama a useRive con el archivo, el nombre de la máquina de estados, autoplay y autoBind: true.
  3. Obtén los setters. Usa useViewModelInstanceNumber para el estado de ánimo y useViewModelInstanceTrigger para las reacciones puntuales.
  4. Vincula el estado de la app con el personaje. En un useEffect, define el estado de ánimo o dispara un trigger cada vez que cambie tu prop de estado.
  5. Renderízalo y define su tamaño. Renderiza RiveComponent dentro de un contenedor con ancho y alto fijos, e importa el componente en tu página de servidor.

Preguntas frecuentes

¿Todavía debo instalar framer-motion?

No. Los proyectos nuevos deben instalar motion e importar desde "motion/react". Los proyectos existentes pueden cambiar desinstalando framer-motion, instalando motion y actualizando las rutas de importación; la guía de actualización de Motion muestra que la API de componentes no cambia, así que el código existente con motion.div sigue funcionando después de actualizar la importación.

¿Por qué aparece "window is not defined" en Next.js?

Una librería que solo funciona en el navegador se ejecutó en el servidor. Agrega "use client" al componente que la usa y, si el error persiste, importa ese componente con next/dynamic y ssr: false para que solo se cargue en el navegador. Dale a la importación dinámica un placeholder con tamaño definido para evitar el layout shift (salto de diseño).

¿Lottie o Rive para una app de React?

Usa Lottie para ilustraciones que se reproducen de principio a fin, como estados vacíos o escenas de onboarding. Usa Rive cuando un personaje deba cambiar de estado en respuesta a eventos de la app, porque un solo archivo con máquina de estados reemplaza varias animaciones separadas y su lógica de cambio. Muchas apps usan ambos: Lottie para las escenas y Rive para la mascota de marca.

¿Cómo respeto el movimiento reducido en React?

Envuelve tu app en <MotionConfig reducedMotion="user"> para las animaciones de Motion, agrega media queries prefers-reduced-motion al CSS y usa useReducedMotion() o matchMedia para pausar los reproductores de Lottie y Rive o mostrar un fotograma estático. Pruébalo activando la opción de reducir movimiento en tu sistema operativo.

Respuestas rápidas de esta guía

Fuentes

Guías relacionadas