Rive no Angular: wrapper da comunidade vs canvas puro
Por equipe Charmotion · Atualizado em

O Rive não tem runtime oficial para Angular. O ng-rive consta como desatualizado, e o wrapper da comunidade listado na documentação do Rive, @grandgular/rive-angular-*, exige Angular 18+ e tem poucos usuários. Para produtos de longa duração, use o runtime web oficial @rive-app/canvas a partir de um componente: crie a instância depois que a view inicializar, controle-a com signals e faça a limpeza ao destruir.
Principais pontos
- O Rive lista o Angular apenas como runtime da comunidade; ele não é dono do projeto nem o mantém.
- O ng-rive consta como desatualizado; os pacotes Grandgular são mais novos, mas pouco adotados.
- O runtime web puro precisa de cerca de 30 linhas em um componente standalone e não tem dependência extra.
- Proteja a criação com
isPlatformBrowser(ou useafterNextRender) para SSR. - Envie os valores dos signals para
viewModelInstance.number("mood")comeffect().
Existe uma biblioteca oficial do Rive para Angular?
Não. A documentação do Rive lista o Angular entre os runtimes da comunidade e afirma que não é um projeto mantido nem pertencente ao Rive. Você tem três opções realistas:
| Opção | Mantenedor | Angular | Status |
|---|---|---|---|
@rive-app/canvas ou @rive-app/webgl2 usados diretamente | Rive (runtime web oficial) | Qualquer | Mais seguro a longo prazo |
@grandgular/rive-angular-webgl2 / -canvas | Comunidade, listado na documentação do Rive | 18+ | Ativo, poucos usuários |
ng-rive | Comunidade | Versões antigas | Consta como desatualizado |
Um wrapper economiza algum código repetitivo, mas adiciona uma dependência que precisa acompanhar tanto as versões do Angular quanto as do Rive. Quando o repositório Grandgular foi verificado para este guia, ele tinha apenas algumas estrelas no GitHub. Isso não significa que o código seja ruim, mas quer dizer que menos pessoas encontram e corrigem problemas. A abordagem direta abaixo usa só o pacote que o próprio Rive publica, então as atualizações do runtime nunca dependem de terceiros.
O ng-rive ainda é mantido?
Trate-o como legado. A documentação antiga do Rive para Angular, que explicava npm install ng-rive, foi ela mesma marcada como desatualizada, e a documentação atual do Rive aponta para o wrapper Grandgular. O ng-rive foi escrito para versões antigas do Angular e é anterior aos componentes standalone e aos signals.
Se um app existente usa o ng-rive e funciona, não há necessidade de removê-lo agora. Planeje a migração na próxima atualização do Angular ou quando for adotar um recurso mais novo dos arquivos Rive. O principal é o Data Binding: o Rive tornou obsoletos os inputs de state machine em favor dos view models, e um wrapper escrito antes do Data Binding não vai oferecê-lo.
O pacote mais antigo do próprio projeto Grandgular, @grandgular/rive-angular, também está obsoleto em favor dos pacotes separados -webgl2 e -canvas. Se você escolher o wrapper, instale os nomes atuais:
npm install @grandgular/rive-angular-webgl2 @rive-app/webgl2
# or
npm install @grandgular/rive-angular-canvas @rive-app/canvas
O README indica Angular 18.0.0 e TypeScript 5.4 como requisitos mínimos. Confira no README os nomes atuais dos componentes e inputs antes de escrever os templates.
Como usar @rive-app/canvas diretamente em um componente Angular?
Instale o runtime com npm install @rive-app/canvas, coloque character.riv em public/ (ou em src/assets/ em estruturas de projeto mais antigas) e crie um componente standalone:
import { AfterViewInit, Component, ElementRef, OnDestroy, PLATFORM_ID,
ViewChild, effect, inject, input } from "@angular/core";
import { isPlatformBrowser } from "@angular/common";
import { Rive } from "@rive-app/canvas";
@Component({
selector: "app-mascot",
standalone: true,
template: '<canvas #c width="320" height="320" role="img" aria-label="Animated mascot"></canvas>',
})
export class MascotComponent implements AfterViewInit, OnDestroy {
mood = input(0);
@ViewChild("c") canvas!: ElementRef<HTMLCanvasElement>;
private platformId = inject(PLATFORM_ID);
private r?: Rive;
constructor() {
effect(() => this.setMood(this.mood()));
}
ngAfterViewInit() {
if (!isPlatformBrowser(this.platformId)) return;
this.r = new Rive({
src: "character.riv",
canvas: this.canvas.nativeElement,
stateMachine: "CharacterViewModelMachine",
autoplay: true,
autoBind: true,
onLoad: () => {
this.r?.resizeDrawingSurfaceToCanvas();
this.setMood(this.mood());
},
});
}
fire(name: "success" | "error" | "yes" | "no" | "enter" | "exit") {
this.r?.viewModelInstance?.trigger(name)?.trigger();
}
private setMood(value: number) {
const prop = this.r?.viewModelInstance?.number("mood");
if (prop) prop.value = value;
}
ngOnDestroy() {
this.r?.cleanup();
}
}
cleanup() libera os objetos C++ que o runtime alocou no WebAssembly, então sempre chame essa função ao destruir o componente.
Como controlar o número mood a partir de um signal?
O componente acima já faz isso: mood é um signal input, e o effect() no construtor roda de novo sempre que ele muda, gravando o valor no view model vinculado. Como o effect apenas lê o signal e escreve no runtime do Rive, ele não dispara a detecção de mudanças nem cria um loop.
O effect pode rodar antes de o arquivo carregar. O setter verifica se viewModelInstance existe, e onLoad aplica o valor atual assim que o arquivo fica pronto, então o primeiro humor nunca se perde.
No componente pai, derive o humor do estado do app e chame fire() para reações únicas:
import { Component, signal } from "@angular/core";
@Component({
selector: "app-save",
standalone: true,
imports: [MascotComponent],
template: `<app-mascot #m [mood]="saving() ? 5 : 0" />
<button (click)="save(m)">Save</button>`,
})
export class SaveComponent {
saving = signal(false);
async save(m: MascotComponent) {
this.saving.set(true); // mood 5: loading
try { await this.api.save(); m.fire("success"); }
catch { m.fire("error"); }
finally { this.saving.set(false); } // mood 0: idle
}
}

Use os humores para estados contínuos (idle, loading, listening) e os triggers para eventos (success, error). Essa divisão se encaixa bem em signals para estado e métodos para eventos.
Como evitar erros de SSR com a renderização no servidor do Angular?
O renderizador de servidor do Angular executa seus componentes no Node.js, onde não há canvas, nem window, nem como rodar o runtime do Rive. Criar a instância no servidor gera erro ou desperdiça processamento. A verificação isPlatformBrowser(this.platformId) em ngAfterViewInit retorna cedo no servidor, então só a marcação do canvas é renderizada ali e a animação começa após a hidratação.
O Angular também oferece afterNextRender, que a documentação do Angular descreve como algo que roda apenas no navegador, após a renderização. É uma alternativa mais limpa à verificação de plataforma:
constructor() {
afterNextRender(() => this.start()); // never runs on the server
effect(() => this.setMood(this.mood()));
}
Em qualquer caso, mantenha o cleanup em ngOnDestroy; com optional chaining, ele não faz nada no servidor. Se o próprio import causar problemas em um build de servidor, carregue o runtime sob demanda dentro do callback exclusivo do navegador com await import("@rive-app/canvas").
Os arquivos do Charmotion são feitos exatamente para essa integração: um .riv com 17 animações a partir do seu personagem ou logo, um view model Character com um número mood de 0 a 9 e seis triggers, além de inputs legados de CharacterStateMachine para wrappers mais antigos. Os conjuntos também incluem código de exemplo para web, React e SwiftUI.
Pacote canvas ou WebGL2: qual escolher?
Os dois pacotes expõem a mesma classe Rive e as mesmas opções, então trocar é só mudar uma linha de import.
@rive-app/canvasdesenha com a API de canvas 2D do navegador. O Rive o recomenda para páginas que exibem muitas animações ao mesmo tempo, porque ele não está sujeito ao limite de contextos WebGL do navegador.@rive-app/webgl2usa o Rive Renderer sobre WebGL2 e suporta todos os recursos, incluindo efeitos que o canvas 2D não consegue desenhar. O Rive o sugere para a maioria das páginas com uma única animação.
Para um painel com um mascote pequeno em vários cards, escolha canvas. Para um personagem grande na seção hero com efeitos avançados, escolha WebGL2. O wrapper Grandgular publica um pacote para cada um, refletindo a mesma escolha.
Seja qual for a escolha, instale só um deles. Os dois incluem o próprio WASM, e enviar dois runtimes dobra o download sem nenhum benefício. Se você trocar depois, o caminho do wasm hospedado por você também muda, então atualize ao mesmo tempo qualquer import de RuntimeLoader.setWasmUrl().
Como adicionar um personagem Rive a um app Angular sem wrapper
- Instale o runtime. Execute
npm install @rive-app/canvase adicione o .riv empublic/. - Crie um componente standalone. Adicione um canvas com uma referência de template e um signal input
mood. - Inicie só no navegador. Crie
new Rive({ stateMachine: "CharacterViewModelMachine", autoBind: true, ... })emngAfterViewInitprotegido porisPlatformBrowser, ou emafterNextRender. - Sincronize o signal. Use
effect()para gravar o humor emviewModelInstance.number("mood"). - Exponha os triggers. Adicione um método
fire(name)que chamatrigger(name).trigger(). - Faça a limpeza. Chame
this.r?.cleanup()emngOnDestroy.
Perguntas frequentes
Devo usar o wrapper Grandgular para Rive?
É o wrapper da comunidade que a documentação do Rive lista atualmente. Ele exige Angular 18 ou posterior e está em desenvolvimento ativo, mas poucos projetos o usam até agora. É uma escolha razoável para protótipos. Para um produto que você vai manter por anos, o runtime web puro evita depender de um pacote pequeno de terceiros.
O Rive funciona com Angular zoneless?
Sim. O runtime do Rive controla seu próprio loop de renderização com requestAnimationFrame e não depende do Zone.js nem da detecção de mudanças. Signals e effect() enviam valores diretamente para o runtime, então o componente acima funciona igual com ou sem zones. Não é preciso nenhum wrapper NgZone.runOutsideAngular.
Onde o arquivo .riv deve ficar em um projeto Angular?
Nos projetos Angular atuais, coloque-o em public/ e carregue-o como character.riv. Estruturas mais antigas servem src/assets/, então o caminho passa a ser assets/character.riv. Qualquer que seja a pasta, ela precisa estar listada na configuração de assets do build, o que os projetos novos já fazem por padrão.
Respostas rápidas deste guia
Fontes
- Community runtimes: Angular, Rive
- Grandgular/rive: Angular wrapper for Rive, GitHub
- ng-rive, npm
- Web (JS) runtime, Rive
- Web runtime: Data binding, Rive
- Canvas vs WebGL2, Rive
- Server-side and hybrid rendering, Angular
- Signals, Angular