Pular para o conteúdo
charmotion

Rive no Astro: islands, client:visible e WASM sob demanda

Por equipe Charmotion · Atualizado em

Ilustração de capa do guia de frameworks e no-code: Rive no Astro: Islands, client:visible e WASM com lazy loading, com personagens de demonstração do Charmotion

Você não precisa de React para usar Rive no Astro. Adicione um canvas e um <script> que importe o runtime web oficial @rive-app/canvas, ou hidrate um componente de mascote já existente em React, Vue ou Svelte como island com client:visible. As duas opções mantêm o WASM do Rive fora do caminho crítico e controlam o personagem com as mesmas chamadas ao view model.

Principais pontos

  • O Astro não tem integração com o Rive; o runtime web funciona em um <script> simples.
  • O Astro empacota e processa as tags <script>, então import de pacotes npm funciona.
  • client:visible hidrata uma island quando ela entra na viewport; client:only pula a renderização no servidor.
  • Use um IntersectionObserver em scripts simples para iniciar sob demanda os mascotes abaixo da dobra.
  • Mostre uma imagem de pôster até o canvas ficar pronto e respeite prefers-reduced-motion.

Preciso de React para usar Rive no Astro?

Não. O Astro entrega zero JavaScript por padrão e não tem integração com o Rive, mas nem precisa. O runtime web do Rive (@rive-app/canvas ou @rive-app/webgl2) é JavaScript puro que desenha em um canvas. Você pode executá-lo a partir de uma tag <script> do Astro sem nenhum framework de UI.

Escolha o caminho de acordo com o que você já tem:

SituaçãoAbordagem
Site de conteúdo, sem framework de UICanvas e um <script> no arquivo .astro
Você já tem um mascote em React usando @rive-app/react-canvasRenderize-o como island com client:visible
Componente Vue ou SvelteMesma abordagem de island, com a integração desse framework

O script simples é a opção mais leve: nenhum runtime de framework é enviado, apenas o JavaScript do Rive e seu WASM. Uma island faz sentido quando o mascote compartilha estado com outros componentes interativos. O guia de React, o guia de Vue e o guia de Svelte tratam desses componentes.

Como adicionar Rive com um script simples em um arquivo .astro?

Instale o runtime com npm install @rive-app/canvas e coloque o arquivo .riv em public/. O Astro processa tags <script> por padrão: ele empacota os imports, gera um módulo e o inclui uma vez por página, mesmo que o componente apareça várias vezes.

---
// 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 o script roda uma vez por página, ele percorre todos os canvas de mascote em vez de supor um único elemento. O observer adia tanto a inicialização do runtime quanto o download do .riv até o personagem aparecer na tela com a rolagem.

Se você usa as view transitions do Astro (o componente <ClientRouter />), os scripts não são executados de novo na navegação do lado do cliente. Coloque a configuração dentro de um listener do evento astro:page-load e chame r.cleanup() antes de a página ser trocada.

Quando usar client:visible ou client:only?

As diretivas do Astro controlam quando um componente de framework é hidratado. Para uma island de mascote, três importam:

DiretivaQuando hidrataUse para
client:loadImediatamente ao carregar a páginaUm mascote na seção hero, visível acima da dobra
client:visibleQuando o componente entra na viewportMascotes mais abaixo na página
client:only="react"Só no cliente; sem HTML renderizado no servidorComponentes que acessam window durante o import ou a renderização
---
import Mascot from "../components/Mascot.jsx";
---
<Mascot client:visible mood={0} />

A documentação do Astro indica que client:visible usa internamente um IntersectionObserver, então você ganha a inicialização sob demanda da seção anterior sem precisar escrevê-la. Ele também aceita a opção rootMargin para começar a hidratar um pouco antes de o componente aparecer na tela.

Recorra a client:only apenas quando a renderização no servidor quebrar, porque ele remove totalmente o HTML do servidor e o espaço fica vazio até o JavaScript rodar. Um componente Rive bem escrito cria a instância em um effect ou hook de montagem, então renderiza sem problemas no servidor e funciona com client:visible.

Como carregar o .riv e o rive.wasm sem prejudicar o LCP?

O runtime web baixa o rive.wasm do unpkg quando a primeira instância é criada. Em um mascote na seção hero, essa requisição, somada ao JavaScript e ao .riv, disputa recursos com o seu largest contentful paint. Três hábitos mantêm isso sob controle:

  • Não deixe o canvas ser o elemento de LCP. Renderize uma imagem de pôster estática com dimensões explícitas no mesmo lugar. O título ou o pôster passa a ser o candidato a LCP, e a animação substitui o pôster quando estiver pronta.
  • Hospede você mesmo e faça preload só acima da dobra. A documentação do Rive mostra RuntimeLoader.setWasmUrl() para servir sua própria cópia, que precisa corresponder à versão do pacote instalado. Para um personagem na seção hero, adicione <link rel="preload"> para o .riv, assim ele carrega em paralelo.
  • Inicie todo o resto sob demanda. Abaixo da dobra, use client:visible ou o padrão com observer para que nada seja baixado antes da hora.
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);

O Astro usa Vite, então o import com ?url copia o wasm para a saída do build com um nome com hash. O Rive recomenda @rive-app/canvas quando uma página exibe muitas animações, já que ele não é limitado pelo teto de contextos WebGL do navegador.

Maquete de site com um título à esquerda e Kiko, um mascote animado, rodando a partir de um arquivo Rive no hero
Um mascote na seção hero de um site: um pequeno arquivo Rive em um canvas ao lado do título.

Como mostrar um pôster estático antes de a animação carregar?

Empilhe uma imagem e o canvas na mesma caixa e esconda a imagem assim que o Rive desenhar o primeiro frame. Definir o tamanho do canvas de antemão evita deslocamento de layout.

<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>

No callback onLoad, adicione a classe: canvas.parentElement.classList.add("ready"). A imagem deve ser o frame idle do personagem, para que a troca fique imperceptível.

O Charmotion entrega as peças que esse padrão precisa: 17 animações a partir de uma imagem de personagem ou logo em poucos minutos, empacotadas como um único .riv com um view model Character (um número mood de 0 a 9 e seis triggers), um exemplo web index.html com data binding, e exportações sob demanda de MP4, GIF e WebM e MOV transparentes para redes sociais e e-mail.

Como respeitar prefers-reduced-motion no Astro?

Verifique a media query no seu script e pule o runtime por completo ou inicie-o pausado.

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

Pular o runtime é a melhor escolha em sites de conteúdo: quem pediu menos movimento também baixa menos. Se o personagem transmite algum significado, como um status, mantenha-o, mas passe autoplay: false para que o runtime desenhe um frame estático e você ainda consiga definir o humor.

A media feature tem dois valores, no-preference e reduce, e reflete a configuração do sistema operacional do visitante. O guia de movimento reduzido explica quais movimentos manter.

Nas islands, aplique a mesma verificação dentro do componente do framework, no hook que cria a instância do Rive, para que os dois caminhos se comportem da mesma forma.

Como adicionar um mascote Rive a uma página Astro

  1. Instale o runtime. Execute npm install @rive-app/canvas e coloque o .riv e uma imagem de pôster em public/.
  2. Adicione a marcação. Crie um componente com um canvas dimensionado e uma imagem de pôster empilhados na mesma caixa.
  3. Adicione o script. Importe Rive em um <script> e crie a instância com stateMachine: "CharacterViewModelMachine" e autoBind: true.
  4. Inicie sob demanda. Use um IntersectionObserver ou renderize um componente de framework com client:visible.
  5. Controle os estados. Defina viewModelInstance.number("mood").value e dispare triggers a partir de event listeners.
  6. Respeite o movimento reduzido. Pule ou pause o runtime quando prefers-reduced-motion: reduce estiver ativo.

Perguntas frequentes

Por que meu script do Rive só roda no primeiro mascote?

O Astro inclui um script processado uma única vez por página, não importa quantas vezes o componente apareça. Selecione todos os canvas correspondentes com querySelectorAll e percorra-os em loop, como no exemplo, em vez de usar getElementById. Assim, cada canvas ganha sua própria instância do Rive.

O Rive continua funcionando depois de uma view transition do Astro?

Não automaticamente. Com as view transitions ativadas, os scripts processados não rodam de novo na navegação do lado do cliente, então as novas páginas mostram um canvas vazio. Execute sua configuração no evento astro:page-load, que dispara no primeiro carregamento e depois de cada navegação, e limpe as instâncias antigas antes da troca.

Posso usar client:idle em uma ilha do Rive?

Sim. client:idle faz a hidratação assim que o navegador fica ocioso, o que combina com um mascote visível no carregamento, mas não essencial. Para qualquer coisa abaixo da primeira dobra, client:visible costuma ser melhor, porque espera até o visitante realmente rolar até o personagem.

Respostas rápidas deste guia

Fontes

Guias relacionados