Ir al contenido
charmotion

Rive en Astro: islas, client:visible y WASM con carga diferida

Por el equipo de Charmotion · Actualizado el

Ilustración de portada de la guía de frameworks y no-code: Rive en Astro: islas, client:visible y WASM con carga diferida, con personajes de demostración de Charmotion

No necesitas React para usar Rive en Astro. Agrega un canvas y un <script> que importe el runtime web oficial @rive-app/canvas, o hidrata como isla con client:visible un componente de mascota de marca que ya tengas en React, Vue o Svelte. Ambas opciones dejan el WASM de Rive fuera de la ruta crítica y controlan el personaje con las mismas llamadas al view model.

Puntos clave

  • Astro no tiene integración con Rive; el runtime web funciona en un <script> simple.
  • Astro empaqueta y procesa las etiquetas <script>, así que import desde npm funciona.
  • client:visible hidrata una isla cuando entra en el viewport; client:only omite el renderizado en el servidor.
  • Usa un IntersectionObserver en scripts simples para iniciar de forma diferida las mascotas que están más abajo en la página.
  • Muestra una imagen de póster hasta que el canvas esté listo y respeta prefers-reduced-motion.

¿Necesito React para usar Rive en Astro?

No. Astro no envía JavaScript por defecto y no tiene integración con Rive, pero no la necesita. El runtime web de Rive (@rive-app/canvas o @rive-app/webgl2) es JavaScript puro que dibuja en un canvas. Puedes ejecutarlo desde una etiqueta <script> de Astro sin ningún framework de UI.

Elige el camino según lo que ya tengas:

SituaciónEnfoque
Sitio de contenido, sin framework de UICanvas más un <script> en el archivo .astro
Ya tienes una mascota en React que usa @rive-app/react-canvasRenderízala como isla con client:visible
Componente de Vue o SvelteEl mismo enfoque de isla con la integración de ese framework

El script simple es la opción más ligera: no se envía ningún runtime de framework, solo el JavaScript de Rive y su WASM. Una isla tiene sentido cuando la mascota comparte estado con otros componentes interactivos. La guía de React, la guía de Vue y la guía de Svelte explican esos componentes.

¿Cómo agrego Rive con un script simple en un archivo .astro?

Instala el runtime con npm install @rive-app/canvas y coloca el archivo en public/. Astro procesa las etiquetas <script> por defecto: empaqueta los imports, genera un módulo y lo incluye una sola vez por página, aunque el componente aparezca varias veces.

---
// src/components/Mascot.astro
---
<canvas class="mascot" width="320" height="320"
        role="img" aria-label="Animated mascot"></canvas>

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

  for (const canvas of document.querySelectorAll("canvas.mascot")) {
    const io = new IntersectionObserver(([entry]) => {
      if (!entry.isIntersecting) return;
      io.disconnect();
      const r = new Rive({
        src: "/character.riv",
        canvas,
        stateMachine: "CharacterViewModelMachine",
        autoplay: true,
        autoBind: true,
        onLoad: () => {
          r.resizeDrawingSurfaceToCanvas();
          r.viewModelInstance.number("mood").value = 2; // wave
        },
      });
      canvas.addEventListener("click", () =>
        r.viewModelInstance.trigger("success").trigger());
    });
    io.observe(canvas);
  }
</script>

Como el script se ejecuta una vez por página, recorre todos los canvas de mascota en lugar de suponer un único elemento. El observer retrasa tanto el inicio del runtime como la descarga del .riv hasta que el personaje aparece al hacer scroll.

Si usas las view transitions de Astro (el componente <ClientRouter />), los scripts no se vuelven a ejecutar en la navegación del lado del cliente. Envuelve la configuración en un listener del evento astro:page-load y llama a r.cleanup() antes de que se reemplace la página.

¿Cuándo uso client:visible y cuándo client:only?

Las directivas de Astro controlan cuándo se hidrata un componente de framework. Para una isla con mascota, importan tres:

DirectivaCuándo se hidrataÚsala para
client:loadDe inmediato al cargar la páginaUna mascota en la sección hero, visible sin hacer scroll
client:visibleCuando el componente entra en el viewportMascotas más abajo en la página
client:only="react"Solo en el cliente; sin HTML renderizado en el servidorComponentes que acceden a window durante el import o el renderizado
---
import Mascot from "../components/Mascot.jsx";
---
<Mascot client:visible mood={0} />

La documentación de Astro indica que client:visible usa internamente un IntersectionObserver, así que te da el inicio diferido de la sección anterior sin que tengas que escribirlo. También acepta la opción rootMargin para empezar a hidratar un poco antes de que el componente aparezca en pantalla.

Recurre a client:only solo cuando el renderizado en el servidor falle, porque elimina por completo el HTML del servidor y el espacio queda vacío hasta que se ejecuta JavaScript. Un componente de Rive bien escrito crea la instancia en un effect o en un hook de montaje, así que se renderiza sin problemas en el servidor y funciona con client:visible.

¿Cómo cargo el .riv y rive.wasm sin afectar el LCP?

El runtime web descarga rive.wasm desde unpkg cuando se crea la primera instancia. En una mascota de la sección hero, esa solicitud, junto con el JavaScript y el .riv, compite con tu largest contentful paint. Tres hábitos lo mantienen bajo control:

  • No dejes que el canvas sea el elemento LCP. Renderiza una imagen de póster estática con dimensiones explícitas en el mismo lugar. El titular o el póster pasa a ser el candidato a LCP, y la animación reemplaza al póster cuando está lista.
  • Aloja los archivos tú mismo y precarga solo lo que está en la primera pantalla. La documentación de Rive muestra RuntimeLoader.setWasmUrl() para servir tu propia copia, que debe coincidir con la versión del paquete instalado. Para un personaje en la sección hero, agrega <link rel="preload"> para el .riv, así se carga en paralelo.
  • Inicia todo lo demás con carga diferida. Más abajo en la página, usa client:visible o el patrón del observer para que nada se descargue hasta que haga falta.
import { RuntimeLoader } from "@rive-app/canvas";
import riveWasmUrl from "@rive-app/canvas/rive.wasm?url";

// Run once, before the first new Rive(...)
RuntimeLoader.setWasmUrl(riveWasmUrl);

Astro usa Vite, así que el import con ?url copia el wasm a la salida del build con un nombre con hash. Rive recomienda @rive-app/canvas cuando una página muestra muchas animaciones, ya que no está limitado por el tope de contextos WebGL del navegador.

Maqueta de un sitio web con un titular a la izquierda y Kiko, una mascota animada, reproduciéndose desde un archivo Rive en el hero
Una mascota de marca en la sección hero de un sitio web: un pequeño archivo Rive en un canvas junto al titular.

¿Cómo muestro un póster estático antes de que cargue la animación?

Apila una imagen y el canvas en la misma caja y oculta la imagen cuando Rive haya dibujado su primer fotograma. Definir el tamaño del canvas desde el principio evita el layout shift (salto de diseño).

<div class="mascot-box">
  <img src="/character-poster.png" width="320" height="320" alt="">
  <canvas class="mascot" width="320" height="320"
          role="img" aria-label="Animated mascot"></canvas>
</div>

<style>
  .mascot-box { position: relative; width: 320px; aspect-ratio: 1; }
  .mascot-box > * { position: absolute; inset: 0; width: 100%; height: 100%; }
  .mascot-box.ready img { display: none; }
</style>

En el callback onLoad, agrega la clase: canvas.parentElement.classList.add("ready"). La imagen debe ser el fotograma en reposo (idle) del personaje, para que el cambio no se note.

Charmotion entrega las piezas que necesita este patrón: 17 animaciones a partir de una imagen de personaje o logo en pocos minutos, empaquetadas como un solo .riv con un view model Character (un número mood de 0 a 9 y seis triggers), un ejemplo web index.html con Data Binding, y exportaciones bajo demanda de MP4, GIF y WebM y MOV transparentes para redes sociales y email.

¿Cómo respeto prefers-reduced-motion en Astro?

Revisa la media query en tu script y omite el runtime por completo o inícialo en pausa.

const reduce = matchMedia("(prefers-reduced-motion: reduce)").matches;
if (!reduce) {
  io.observe(canvas);   // start the animation as usual
}
// otherwise the poster image stays visible

Omitir el runtime es la mejor opción en sitios de contenido: los visitantes que pidieron menos movimiento también descargan menos. Si el personaje transmite información, como un estado, mantenlo pero pasa autoplay: false para que el runtime dibuje un fotograma fijo y puedas seguir cambiando el estado de ánimo.

La media feature tiene dos valores, no-preference y reduce, y refleja la configuración del sistema operativo del visitante. La guía de movimiento reducido explica qué movimiento conservar.

En las islas, aplica la misma comprobación dentro del componente del framework, en el hook que crea la instancia de Rive, para que ambos caminos se comporten igual.

Cómo agregar una mascota de marca de Rive a una página de Astro

  1. Instala el runtime. Ejecuta npm install @rive-app/canvas y coloca el .riv y una imagen de póster en public/.
  2. Agrega el marcado. Crea un componente con un canvas de tamaño definido y una imagen de póster apilados en la misma caja.
  3. Agrega el script. Importa Rive en un <script> y créalo con stateMachine: "CharacterViewModelMachine" y autoBind: true.
  4. Inicia de forma diferida. Usa un IntersectionObserver o renderiza un componente de framework con client:visible.
  5. Controla los estados. Define viewModelInstance.number("mood").value y dispara triggers desde los event listeners.
  6. Respeta el movimiento reducido. Omite o pausa el runtime cuando coincida prefers-reduced-motion: reduce.

Preguntas frecuentes

¿Por qué mi script de Rive solo se ejecuta para la primera mascota?

Astro incluye un script procesado una sola vez por página, sin importar cuántas veces aparezca el componente. Selecciona todos los canvas que coincidan con querySelectorAll y recórrelos en un loop, como en el ejemplo, en lugar de usar getElementById. Así cada canvas obtiene su propia instancia de Rive.

¿Rive sigue funcionando después de una view transition de Astro?

No de forma automática. Con las view transitions activadas, los scripts procesados no se vuelven a ejecutar en la navegación del lado del cliente, así que las páginas nuevas muestran un canvas vacío. Ejecuta tu configuración en el evento astro:page-load, que se dispara en la primera carga y después de cada navegación, y limpia las instancias anteriores antes del cambio.

¿Puedo usar client:idle para una isla de Rive?

Sí. client:idle hidrata en cuanto el navegador está inactivo, lo que funciona bien para una mascota visible al cargar pero no esencial. Para cualquier cosa más abajo en la página, client:visible suele ser mejor, porque espera hasta que el visitante realmente hace scroll hasta el personaje.

Respuestas rápidas de esta guía

Fuentes

Guías relacionadas