Rive no Astro: islands, client:visible e WASM sob demanda
Por equipe Charmotion · Atualizado em

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ãoimportde pacotes npm funciona. client:visiblehidrata uma island quando ela entra na viewport;client:onlypula a renderização no servidor.- Use um
IntersectionObserverem 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ção | Abordagem |
|---|---|
| Site de conteúdo, sem framework de UI | Canvas e um <script> no arquivo .astro |
Você já tem um mascote em React usando @rive-app/react-canvas | Renderize-o como island com client:visible |
| Componente Vue ou Svelte | Mesma 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:
| Diretiva | Quando hidrata | Use para |
|---|---|---|
client:load | Imediatamente ao carregar a página | Um mascote na seção hero, visível acima da dobra |
client:visible | Quando o componente entra na viewport | Mascotes mais abaixo na página |
client:only="react" | Só no cliente; sem HTML renderizado no servidor | Componentes 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:visibleou 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.

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
- Instale o runtime. Execute
npm install @rive-app/canvase coloque o .riv e uma imagem de pôster empublic/. - Adicione a marcação. Crie um componente com um canvas dimensionado e uma imagem de pôster empilhados na mesma caixa.
- Adicione o script. Importe
Riveem um<script>e crie a instância comstateMachine: "CharacterViewModelMachine"eautoBind: true. - Inicie sob demanda. Use um
IntersectionObserverou renderize um componente de framework comclient:visible. - Controle os estados. Defina
viewModelInstance.number("mood").valuee dispare triggers a partir de event listeners. - Respeite o movimento reduzido. Pule ou pause o runtime quando
prefers-reduced-motion: reduceestiver 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
- Template directives reference, Astro
- Scripts and event handling, Astro
- Web (JS) runtime, Rive
- Web runtime: Data binding, Rive
- Preloading WASM, Rive
- prefers-reduced-motion, MDN Web Docs