Rive no Svelte e SvelteKit (guia de Svelte 5)
Por equipe Charmotion · Atualizado em

O Svelte não tem um runtime oficial do Rive, então você usa diretamente o runtime web @rive-app/canvas. Crie a instância dentro de onMount, que nunca roda durante a renderização no servidor, com autoBind: true. Atualize o número mood do view model a partir de um $effect, dispare triggers nos event handlers e retorne uma função de limpeza síncrona que chame r.cleanup().
Principais pontos
- O Rive não lista runtime para Svelte;
@rive-app/canvasfunciona em qualquer componente Svelte 5. onMountnão roda no servidor, então o SSR do SvelteKit pode continuar ativado.- Mantenha o callback de
onMountsíncrono para que a função de limpeza retornada seja executada. - Guarde a instância em uma variável
$state.rawpara que o$effectrode de novo assim que ela existir. - Coloque os arquivos .riv em
static/e carregue-os a partir do caminho raiz.
O Rive tem um pacote para Svelte?
Não oficialmente. A documentação do Rive cobre runtimes oficiais para web, React, Apple, Android, Flutter, React Native e game engines, e lista quatro runtimes da comunidade: Angular, C#, Qt e RiveCMP. Não há entrada para Svelte nem SvelteKit. Existem alguns wrappers da comunidade no npm, mas são projetos pequenos que podem ficar defasados em relação ao runtime.
O caminho confiável é o próprio runtime web do Rive, que é independente de framework. Um componente Svelte já oferece tudo o que ele precisa: um elemento canvas via bind:this, um hook de montagem e um hook de desmontagem.
| Necessidade | Pacote |
|---|---|
| Personagem interativo com estados | @rive-app/canvas (ou @rive-app/webgl2) |
| Clipe Lottie linear | @lottiefiles/dotlottie-svelte |
Instale com npm install @rive-app/canvas. O Rive recomenda o pacote canvas quando uma página mostra muitas animações ao mesmo tempo, já que ele não está sujeito ao limite de contextos WebGL do navegador. O pacote WebGL2 tem a mesma API, caso você precise do Rive Renderer.
Como montar o Rive no onMount com o SSR ativado?
A documentação do Svelte diz que onMount não roda em um componente renderizado no servidor, então criar a instância do Rive ali é seguro para SSR sem desativá-lo. Veja um componente Svelte 5 completo:
<script>
import { onMount } from "svelte";
import { Rive } from "@rive-app/canvas";
let { mood = 0 } = $props();
let canvas;
let r = $state.raw(null);
onMount(() => {
const instance = new Rive({
src: "/character.riv",
canvas,
stateMachine: "CharacterViewModelMachine",
autoplay: true,
autoBind: true,
onLoad: () => {
instance.resizeDrawingSurfaceToCanvas();
r = instance; // ready: the $effect below runs now
},
});
return () => instance.cleanup(); // sync callback, so this runs
});
$effect(() => {
const prop = r?.viewModelInstance?.number("mood");
if (prop) prop.value = mood;
});
export function fire(name) {
r?.viewModelInstance?.trigger(name)?.trigger();
}
</script>
<canvas bind:this={canvas} width="320" height="320"
role="img" aria-label="Animated mascot"></canvas>
O import estático funciona no SvelteKit porque nada roda até onMount. Se algum bundler ou pacote ainda reclamar durante a renderização no servidor, importe-o dinamicamente dentro do hook ou proteja o código fora dos componentes com browser de $app/environment.
Como vincular uma prop mood ao view model com $effect?
$effect roda de novo sempre que um valor reativo lido por ele muda. No componente acima, ele lê dois: a prop mood e r. Por isso r é declarado com $state.raw e atribuído em onLoad: o effect roda primeiro com r ainda nulo, não faz nada e roda de novo assim que o arquivo fica pronto, aplicando o humor inicial. Depois disso, cada mudança de prop escreve direto no view model.
Usar $state.raw em vez de $state faz diferença. O $state comum torna objetos profundamente reativos com proxies, algo que você não quer em volta de um objeto de runtime baseado em WASM. A variante raw só rastreia reatribuições.
O componente pai controla o mascote com props comuns e uma referência ao componente:
<script>
import Mascot from "$lib/Mascot.svelte";
let mascot;
let saving = $state(false);
async function save() {
saving = true; // mood 5: loading
try { await api.save(); mascot.fire("success"); }
catch { mascot.fire("error"); }
finally { saving = false; } // mood 0: idle
}
</script>
<Mascot bind:this={mascot} mood={saving ? 5 : 0} />
<button onclick={save}>Save</button>

Humores são estados, então ficam nas props. Triggers são eventos pontuais, então passam pela função exportada fire().
Onde coloco os arquivos .riv no SvelteKit?
Coloque-os em static/. O SvelteKit serve essa pasta a partir da raiz do site, então static/character.riv carrega como /character.riv, exatamente como no componente acima. Os arquivos ali são copiados como estão, sem hash, então dê um novo nome a um arquivo alterado (por exemplo character-v2.riv) ou defina headers de cache curtos se você o substitui com frequência.
Um arquivo .riv costuma ser pequeno. O download maior é o rive.wasm do runtime, que o runtime web busca no unpkg por padrão. Para servi-lo você mesmo, informe ao runtime onde ele está antes de criar a primeira instância:
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 import ?url do Vite copia o wasm para o seu build e retorna a URL dele. A documentação do Rive exige que a versão do wasm corresponda ao pacote @rive-app instalado, e importá-lo do próprio pacote garante isso. Hospedar por conta própria vale a pena em apps que funcionam offline, com políticas de segurança de conteúdo rígidas e em páginas que pré-carregam o mascote.
Como fazer a limpeza ao destruir o componente?
Retorne de onMount uma função que chame cleanup() na instância, como mostrado acima. A documentação do Rive explica que cleanup() remove os objetos C++ de baixo nível criados durante a vida da instância; se você pular essa etapa, haverá vazamento de memória a cada navegação que remonta o componente.
Há uma armadilha. O Svelte só usa a função retornada quando o callback passado a onMount é síncrono. Um callback async retorna uma promise, então sua limpeza é ignorada sem aviso. Se você precisar usar await em um import dinâmico, use onDestroy para a limpeza ou mantenha o callback externo síncrono:
onMount(() => {
let instance;
import("@rive-app/canvas").then(({ Rive }) => {
instance = new Rive({ /* same options as above */ });
});
return () => instance?.cleanup();
});
Também interrompa o que você iniciou fora do Rive: remova event listeners e desconecte qualquer IntersectionObserver usado para iniciar a animação sob demanda.
Como respeitar o prefers-reduced-motion?
Verifique a media query antes de reproduzir e mantenha o personagem parado quando ela estiver ativa. prefers-reduced-motion informa a configuração do sistema operacional do visitante e tem dois valores, no-preference e reduce.
const reduce = matchMedia("(prefers-reduced-motion: reduce)").matches;
const instance = new Rive({
// ...same options as above
autoplay: !reduce,
});
Com autoplay: false, o runtime ainda desenha o primeiro frame da state machine, então o mascote aparece em uma pose estática. Você ainda pode definir o humor para que a pose combine com a situação. Chame isso dentro de onMount, porque matchMedia não existe no servidor.
Para um arquivo pronto, o Charmotion transforma uma imagem de personagem ou logo em 17 animações em poucos minutos, entregues como um único .riv com um view model Character: um número mood (0 idle, 1 happy, 2 wave, 3 thinking, 4 sad, 5 loading, 6 listening, 7 talking, 8 sleeping, 9 attention) e os triggers success, error, yes, no, enter e exit, junto com exportações sob demanda de MP4, GIF e WebM e MOV transparentes.
Rive ou dotLottie no Svelte: qual usar?
Use dotLottie para um clipe que toca sempre do mesmo jeito, e Rive para um personagem que reage ao seu app.
- dotLottie:
npm install @lottiefiles/dotlottie-sveltee depois renderize o componenteDotLottieSveltecom uma fonte .lottie ou .json. Bom para checks de sucesso, ilustrações de onboarding e loaders. - Rive: um único .riv contém idle, loading, success e todos os outros estados, com as transições controladas pela state machine. Seu código Svelte só escreve um número ou dispara um trigger.
A pergunta decisiva é se o seu código precisa dizer o que acontece em seguida. Se a resposta for "continuar em loop até a requisição terminar e depois comemorar", uma state machine poupa você de lidar com vários arquivos e de sincronizar sozinho a passagem de um para outro. Veja Rive vs Lottie para o comparativo completo.
Como adicionar um personagem Rive a um app SvelteKit
- Instale o runtime. Execute
npm install @rive-app/canvase copie o .riv parastatic/. - Crie o componente. Vincule um canvas com
bind:thise crie a instância do Rive em umonMountsíncrono comautoBind: true. - Acompanhe quando está pronto. Atribua a instância a uma variável
$state.rawemonLoad. - Vincule o humor. Em
$effect, definar.viewModelInstance.number("mood").valuea partir da prop. - Dispare triggers. Exporte uma função que chama
trigger(name).trigger()no view model. - Faça a limpeza. Retorne
() => instance.cleanup()doonMount.
Perguntas frequentes
Preciso desativar o SSR para usar Rive no SvelteKit?
Não. A documentação do Svelte informa que onMount não roda durante a renderização no servidor, então criar a instância do Rive ali mantém a página renderizada no servidor. Só a marcação do canvas é enviada pelo servidor, e a animação começa quando o componente é montado no navegador. Definir ssr = false para a rota é desnecessário.
Por que o cleanup do meu onMount não roda?
Provavelmente seu callback de onMount é async. O Svelte só usa a função de cleanup retornada quando o callback é síncrono, porque uma função async retorna uma promise. Mantenha o callback síncrono e trate o import dinâmico com .then(), ou mova o cleanup para onDestroy.
Isso funciona no Svelte 4?
As chamadas ao runtime são as mesmas. Substitua $props() por export let mood = 0, substitua $effect por uma declaração reativa como $: if (r) setMood(mood) e transforme r em um let comum. onMount, bind:this e a função de cleanup funcionam da mesma forma.
Respostas rápidas deste guia
Fontes
- Web (JS) runtime, Rive
- Web runtime: Data binding, Rive
- Preloading WASM, Rive
- Lifecycle hooks, Svelte
- $effect, Svelte
- $app/environment, SvelteKit
- dotlottie-web: players for web, React, Vue, Svelte and web components, LottieFiles on GitHub
- prefers-reduced-motion, MDN Web Docs