Ir al contenido
Tiempo limitadoStarter ahora cuesta 9 US$ al mes en lugar de 20 US$.Ver planes
charmotion

Animación de logo y mascota con el scroll: CSS, Rive y video

Por el equipo de Charmotion · Actualizado el

Ilustración de portada de la guía: Animación de logo y mascota con scroll: CSS, Rive y video, con personajes de demostración de Charmotion

Para un logo que se mueve con el scroll, usa animaciones CSS controladas por scroll: animation-timeline con scroll() o view(), animando transform y opacity. Desde noviembre de 2026 funcionan en las versiones actuales de Chrome, Edge, Firefox y Safari, así que envuélvelas en @supports y deja que los navegadores antiguos muestren el logo en reposo. Para una mascota de Rive, asigna el progreso del scroll a un número con Data Binding.

Puntos clave

  • Las animaciones CSS controladas por scroll pasaron a ser Baseline (recién disponible) en octubre de 2026: Chrome y Edge 115, Firefox 159 y Safari 26 admiten animation-timeline.
  • Usa view() para animar un elemento mientras atraviesa el viewport y scroll() para vincular el movimiento a cuánto se ha desplazado la página. Declara animation-timeline después de la propiedad abreviada animation.
  • Para revelaciones que se reproducen una vez, un Intersection Observer que inicia un video o dispara un trigger de Rive es más sencillo y también funciona en navegadores antiguos.
  • Rive declaró obsoleto scrub() en el runtime v2.41.0. En su lugar, controla un blend state 1D con un número con Data Binding.
  • Pon cada animación de scroll detrás de prefers-reduced-motion: no-preference y haz que el estado estático sea el logo terminado, en reposo.

¿Ya tienes un personaje o logo? Mira ejemplos en pantallas reales de apps: pantallas de splash, onboarding, carga y estado vacío, con la animación que se reproduce en cada una.

¿Qué técnica de animación con scroll encaja con un logo o una mascota?

Hay dos efectos distintos a los que la gente llama “animación con scroll”, y necesitan herramientas diferentes:

  • Vinculada al scroll (scrub): el progreso de la animación sigue la posición del scroll. Si vuelves a subir, se reproduce al revés. Un logo de header que se encoge al hacer scroll está vinculado al scroll.
  • Activada por scroll: la animación se reproduce a su propio ritmo cuando algo entra en pantalla. Una mascota que saluda cuando aparece su sección está activada por scroll.
TécnicaTipoIdeal paraCosto
CSS animation-timeline: view() o scroll()Vinculada al scrollLogos SVG o de imagen: aparecer, encoger, rotar, desvanecerSin JavaScript; solo transform y opacity para que sea fluido
Intersection Observer + video o trigger de RiveActivada por scrollUna mascota que reacciona una vez, una intro de logo cuando aparece una secciónUnas pocas líneas de JavaScript; funciona en todas partes
Número con Data Binding de Rive que controla un blend stateVinculada al scrollPoses de personaje que siguen el scroll: mirar hacia abajo, girarse, una mascota de progresoRuntime de Rive más un manejador de scroll
Scrub de video con currentTimeVinculada al scrollGiros de logo 3D y rotaciones de producto prerrenderizadosArchivos grandes; el costo de búsqueda varía según la codificación y el dispositivo

Para la mayoría de los logos, CSS basta. Recurre a Rive cuando lo que se mueve tiene cara y necesita poses reales, y al video cuando el movimiento es una toma 3D renderizada que ningún runtime puede dibujar en vivo.

¿Cómo funcionan las animaciones CSS controladas por scroll?

MDN las describe como una forma de “animar valores de propiedades a lo largo de una línea de tiempo basada en el scroll en lugar de la línea de tiempo del documento basada en el tiempo, que es la predeterminada”. Escribes @keyframes normales y luego los vinculas a una línea de tiempo con animation-timeline:

  • scroll() crea una línea de tiempo de progreso de scroll para “el elemento raíz, el contenedor de scroll más cercano o el propio elemento”. 0% es el inicio, 100% es el final del rango de scroll.
  • view() crea una línea de tiempo de progreso de vista que “avanza según los cambios de visibilidad de un elemento dentro de su contenedor de scroll más cercano”. Por defecto va desde el momento en que el elemento aparece por un borde hasta que sale por el otro.
  • animation-range acota la parte de la línea de tiempo que usa la animación, por ejemplo entry 0% entry 100% para que termine justo cuando el elemento ha entrado por completo.

Un logo que sube y se asienta cuando su sección entra con el scroll:

@keyframes logo-in {
  from { opacity: 0; transform: translateY(32px) scale(0.85) rotate(-6deg); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .section-logo {
      animation: logo-in 1ms linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 40%;
    }
  }
}

Un logo de header que se encoge durante los primeros 200 píxeles de scroll de la página:

@keyframes logo-shrink {
  to { transform: scale(0.7); }
}

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: scroll()) {
    .site-logo {
      transform-origin: left center;
      animation: logo-shrink 1ms linear both;
      animation-timeline: scroll(root);
      animation-range: 0px 200px;
    }
  }
}

Hay una trampa en la que cae casi todo el mundo: la propiedad abreviada animation restablece animation-timeline. MDN dice que “debes declarar animation-timeline después de declarar cualquier propiedad abreviada animation para que tenga efecto”, y la guía de Chrome da la misma advertencia. La duración de 1ms es intencional: en una línea de tiempo de scroll, la animación se estira de todos modos sobre el rango de scroll, pero MDN señala que Firefox necesita una duración definida para aplicar la animación y aconseja 1ms. Usa easing linear en la línea de tiempo para que el logo siga la barra de scroll de forma uniforme; pon el carácter en los keyframes.

¿Qué navegadores admiten animation-timeline en noviembre de 2026?

Todos los motores principales actuales. En noviembre de 2026, MDN marca animation-timeline, view() y animation-range como “Baseline 2026, recién disponible”, con soporte en los navegadores más recientes “desde octubre de 2026”.

NavegadorPrimera versión con animation-timeline
Chrome y Edge115
Firefox159
Safari (macOS e iOS)26
Samsung Internet23

Can I use sitúa el soporte global en torno al 89% de los usuarios en noviembre de 2026. “Recién disponible” significa que los dispositivos antiguos aún no lo tienen: cualquiera con Safari 18 o un Firefox anterior. Por eso cada ejemplo de arriba está dentro de @supports.

La alternativa debe ser el estado final, no un logo oculto. Escribe los estilos normales del elemento como el logo en reposo y agrega el movimiento solo dentro del bloque @supports. Así, los navegadores sin soporte muestran un logo estático y correcto, que es también lo que ven los usuarios con movimiento reducido. Si el movimiento también importa en navegadores antiguos, usa el enfoque con Intersection Observer de abajo como alternativa que se reproduce una vez; no publiques un polyfill de eventos de scroll solo para un logo.

¿Cómo reproduces la animación de un logo o una mascota cuando entra en pantalla con el scroll?

Usa un Intersection Observer. MDN incluye “decidir si realizar o no tareas o procesos de animación según si el usuario verá el resultado” entre sus usos principales, y es Baseline ampliamente disponible (desde marzo de 2019). Esta versión reproduce videos silenciados mientras al menos la mitad es visible y los pausa en caso contrario:

<video data-play-on-view muted playsinline loop preload="none"
       width="720" height="720" poster="/mascot-wave.webp">
  <source src="/mascot-wave.webm" type="video/webm">
  <source src="/mascot-wave.mp4" type="video/mp4">
</video>
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)");

const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    const video = entry.target;
    if (entry.isIntersecting && !reduce.matches) {
      video.play().catch(() => {});
    } else {
      video.pause();
    }
  }
}, { threshold: 0.5 });

document.querySelectorAll("video[data-play-on-view]").forEach((v) => observer.observe(v));

El atributo muted importa: la guía de autoplay de MDN dice que el bloqueo de reproducción automática no se aplica cuando “la pista de audio está silenciada”, así que play() sin un gesto del usuario solo funciona con video sin sonido. El .catch() evita que una reproducción bloqueada registre un error.

Para una reacción puntual, como una intro de logo o un saludo de mascota, llama a observer.unobserve(entry.target) después de la primera reproducción para que no se reinicie cada vez que el visitante pasa con el scroll. Con un archivo de Rive, el mismo observer puede disparar un trigger en lugar de llamar a play() (consulta la siguiente sección) y pausar la instancia de Rive con r.pause() cuando sale del viewport.

¿Cómo haces scrub de una animación de Rive con el scroll?

A través de la máquina de estados, no del timeline. El runtime web de Rive todavía tiene scrub(), pero su documentación lo marca como “Obsoleto en v2.41.0, usa una máquina de estados para controlar la reproducción”. La forma admitida es un blend state 1D: la documentación de Rive dice que mezcla timelines “con una sola propiedad numérica del view model” y menciona las “interacciones de scroll” entre sus usos. En el editor, pon la pose inicial en un timeline y la final en otro, agrega un blend state 1D controlado por una propiedad numérica (por ejemplo scrollProgress, rango de 0 a 100) y exporta.

En tiempo de ejecución, asigna ese número a partir del progreso del scroll con Data Binding:

import { Rive } from "@rive-app/canvas";

const section = document.querySelector("#story");
const canvas = document.querySelector("#mascot");
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

const r = new Rive({
  src: "/mascot.riv",
  canvas,
  stateMachine: "State Machine 1",
  autoplay: true,
  autoBind: true,
  onLoad: () => {
    r.resizeDrawingSurfaceToCanvas();
    const progress = r.viewModelInstance?.number("scrollProgress");
    if (!progress || reduce) return;

    let queued = false;
    const update = () => {
      queued = false;
      const box = section.getBoundingClientRect();
      const range = box.height - window.innerHeight;
      const p = range > 0 ? Math.min(Math.max(-box.top / range, 0), 1) : 0;
      progress.value = Math.round(p * 100);
    };
    window.addEventListener("scroll", () => {
      if (!queued) {
        queued = true;
        requestAnimationFrame(update);
      }
    }, { passive: true });
    update();
  },
});

Por qué está escrito así: el listener de scroll es passive y hace su trabajo como máximo una vez por fotograma en requestAnimationFrame, así que el scroll nunca lo espera. Redondear a pasos enteros hace que la máquina de estados solo avance cuando el valor cambia, y Rive indica que una máquina de estados sin nada que hacer “se asienta” y deja de avanzar. Los archivos antiguos que usan inputs de la máquina de estados en lugar de un view model siguen funcionando con r.stateMachineInputs(), pero Rive ahora considera obsoleto asignar inputs directamente y recomienda Data Binding.

No todo momento del scroll necesita scrub. A menudo basta con un cambio de estado de ánimo: la mascota se ve curiosa arriba de la página y contenta en la tabla de precios. El archivo de Rive de un set de personaje de Charmotion ya expone un número mood que cambia sus estados de ánimo en loop y un trigger por cada reacción puntual, como success, mediante Data Binding. Asignar mood desde un Intersection Observer da una mascota que responde al scroll sin crear un blend state. El README de cada set lista los valores. Consulta cómo funciona.

¿Se puede hacer scrub de un video con el scroll?

Sí. Asignar currentTime hace una búsqueda: MDN dice que “cambiar el valor de currentTime lleva el medio al nuevo tiempo”, y el navegador puede ajustar la posición a una que el medio admita. Asigna el progreso del scroll al tiempo dentro de requestAnimationFrame:

const video = document.querySelector("#logo-turn");
const track = document.querySelector("#logo-turn-track");
let queued = false;

function update() {
  queued = false;
  if (!video.duration) return;
  const box = track.getBoundingClientRect();
  const range = box.height - window.innerHeight;
  const p = range > 0 ? Math.min(Math.max(-box.top / range, 0), 1) : 0;
  video.currentTime = p * video.duration;
}

window.addEventListener("scroll", () => {
  if (!queued) {
    queued = true;
    requestAnimationFrame(update);
  }
}, { passive: true });

Funciona mejor con tomas cortas y prerrenderizadas, como un logo 3D que gira 90 grados, y tiene costos reales:

  • Buscar es trabajo de decodificación. Qué tan fluida es una búsqueda depende de cómo se codificó el video y del dispositivo. Codificar con keyframes más frecuentes suele ayudar, a costa de un archivo más grande. Prueba en un teléfono de gama media.
  • El archivo completo tiene que estar disponible. No combines el scrub con preload="none"; precárgalo cuando la sección esté cerca.
  • Que sea corto y pequeño. Unos segundos, al tamaño en que se muestra. Para un logo que solo necesita rotar o escalar, los transforms CSS sobre un SVG o una imagen hacen lo mismo sin ningún video.
  • Dale un póster que muestre el fotograma de reposo, que también es el estado con movimiento reducido y sin JavaScript.

¿Cómo mantienes la animación con scroll fluida y cómoda?

Primero, fluida. La guía de Chrome explica por qué los eventos de scroll son una mala base para animar: los navegadores hacen scroll en un proceso separado y entregan los eventos de scroll de forma asíncrona, y “las animaciones en el hilo principal están expuestas a tirones”. Las animaciones CSS controladas por scroll evitan eso y pueden ejecutarse fuera del hilo principal. Que sean ligeras:

  • Anima transform y opacity. Cambiar top, left, el ancho o el alto al hacer scroll genera trabajo de layout y, según web.dev, layout shifts.
  • Un elemento con scrub por pantalla. Un logo, una mascota o una toma de producto, no los tres.
  • Listeners pasivos y una actualización por fotograma para todo lo que se controla desde JavaScript, como en los ejemplos de arriba.
  • Pausa lo que no se ve. Un canvas de Rive o un video que salió de pantalla debería dejar de dibujar.

Luego, cómoda. El movimiento vinculado al scroll es el que más probablemente moleste a las personas con trastornos vestibulares. La explicación del W3C sobre SC 2.3.3 (Animación por interacciones, nivel AAA) menciona el scroll con parallax como una animación a menudo no esencial y dice que el movimiento activado por scroll “puede desencadenar trastornos vestibulares”, con reacciones como “distracción, mareo, dolores de cabeza y náuseas”. Reglas prácticas:

  • Pon cada animación de scroll dentro de @media (prefers-reduced-motion: no-preference) o comprueba matchMedia en JavaScript, como hacen todos los ejemplos de aquí.
  • Que el movimiento sea pequeño: unas decenas de píxeles, unos pocos grados. Los zooms grandes y el parallax a pantalla completa son donde empieza la incomodidad.
  • Nunca ocultes contenido detrás de una animación de scroll. Si la animación falla o está desactivada, el logo y el texto tienen que seguir ahí.

Hay más sobre el ajuste de movimiento reducido en animación accesible y movimiento reducido.

Cómo agregar una animación de logo con scroll y una alternativa

  1. Da estilo al logo en reposo. Escribe el CSS normal del logo como su estado final y estático. Eso es lo que verán los navegadores antiguos y los usuarios con movimiento reducido.
  2. Escribe los keyframes. Anima solo transform y opacity, desde la pose inicial hasta la pose de reposo.
  3. Vincula una línea de tiempo. Dentro de @supports (animation-timeline: view()), define la propiedad abreviada animation, luego animation-timeline: view() o scroll() y después animation-range.
  4. Respeta los ajustes de movimiento. Envuelve el bloque en @media (prefers-reduced-motion: no-preference).
  5. Agrega una alternativa que se reproduzca una vez si hace falta. En navegadores antiguos, inicia una animación puntual con un Intersection Observer en lugar de un listener de scroll.
  6. Prueba. Haz scroll lento y rápido en un teléfono de gama media, vuelve a subir y revisa el estado estático con el movimiento reducido activado.

Preguntas frecuentes

¿Por qué mi animation-timeline no hace nada?

La causa más común es el orden: la propiedad abreviada animation restablece animation-timeline, así que declara animation-timeline después. Revisa también que el elemento tenga un contenedor de scroll en el eje correcto (view() queda inactivo si ese eje no hace scroll) y que el navegador lo admita: Chrome 115, Firefox 159 y Safari 26 o posteriores.

¿Todavía necesito un polyfill de animaciones controladas por scroll en noviembre de 2026?

Normalmente no para un logo. Las animaciones CSS controladas por scroll funcionan en las versiones actuales de Chrome, Edge, Firefox y Safari desde noviembre de 2026. En navegadores antiguos, muestra el logo en reposo o reproduce una animación puntual con un Intersection Observer en lugar de cargar un polyfill.

¿Una animación de Rive puede seguir la posición del scroll?

Sí. Crea en Rive un blend state 1D controlado por una propiedad numérica del view model y luego asigna ese número a partir del progreso del scroll en un listener de scroll pasivo, como máximo una vez por fotograma. El método scrub() de Rive todavía existe, pero está obsoleto desde el runtime v2.41.0.

Fuentes

Guías relacionadas