कंटेंट पर जाएँ
charmotion

Angular में Rive: कम्युनिटी wrapper बनाम सादा Canvas

Charmotion टीम द्वारा · अपडेट:

frameworks और no-code गाइड का कवर इलस्ट्रेशन: Angular में Rive: Community Wrapper बनाम Plain Canvas, Charmotion के डेमो कैरेक्टर के साथ

Rive का कोई official Angular रनटाइम नहीं है। ng-rive को docs में पुराना बताया गया है, और Rive के docs में दिया गया community wrapper, @grandgular/rive-angular-*, Angular 18+ माँगता है और इसके users कम हैं। लंबे समय तक चलने वाले products के लिए, किसी component से official web रनटाइम @rive-app/canvas को सीधे कॉल करें: view initialize होने के बाद इसे बनाएँ, signals से चलाएँ, और destroy पर cleanup करें।

मुख्य बातें

  • Rive, Angular को सिर्फ़ community रनटाइम के रूप में सूचीबद्ध करता है; न वह इसका मालिक है, न इसे maintain करता है।
  • ng-rive को docs में पुराना बताया गया है; Grandgular packages नए हैं, लेकिन कम इस्तेमाल होते हैं।
  • सीधे web रनटाइम के लिए एक standalone component में लगभग 30 लाइनें काफ़ी हैं, और कोई अतिरिक्त dependency नहीं लगती।
  • SSR के लिए instance बनाने से पहले isPlatformBrowser से जाँच करें (या afterNextRender इस्तेमाल करें)।
  • effect() से signal values को viewModelInstance.number("mood") में भेजें।

क्या Rive की कोई official Angular library है?

नहीं। Rive के docs Angular को community runtimes में रखते हैं और साफ़ कहते हैं कि यह Rive का अपना या उसके द्वारा maintain किया जाने वाला project नहीं है। आपके पास तीन व्यावहारिक विकल्प हैं:

विकल्पMaintainerAngularस्टेटस
@rive-app/canvas या @rive-app/webgl2 का सीधा इस्तेमालRive (official web रनटाइम)कोई भीलंबे समय के लिए सबसे सुरक्षित
@grandgular/rive-angular-webgl2 / -canvasCommunity, Rive के docs में सूचीबद्ध18+Active, कम users
ng-riveCommunityपुराने versionsdocs में पुराना बताया गया

wrapper से कुछ boilerplate बचता है, लेकिन इससे एक dependency जुड़ जाती है जिसे Angular और Rive दोनों की releases के साथ तालमेल रखना पड़ता है। इस गाइड के लिए जब Grandgular repository देखी गई, तब उसके GitHub पर गिने-चुने stars ही थे। इसका मतलब यह नहीं कि code खराब है, पर इसका मतलब है कि कम लोग problems ढूँढते और ठीक करते हैं। नीचे दिया गया सीधा तरीका सिर्फ़ वही package इस्तेमाल करता है जो Rive खुद ship करता है, इसलिए रनटाइम upgrades के लिए कभी किसी third party का इंतज़ार नहीं करना पड़ता।

क्या ng-rive अब भी maintain होता है?

इसे legacy मानें। Rive के पुराने Angular documentation को, जिसमें npm install ng-rive समझाया गया था, खुद पुराना चिह्नित किया गया था, और Rive के मौजूदा docs इसकी जगह Grandgular wrapper की ओर इशारा करते हैं। ng-rive पुराने Angular versions के लिए लिखा गया था और standalone components व signals दोनों से पहले का है।

अगर किसी मौजूदा ऐप में यह इस्तेमाल हो रहा है और ठीक चल रहा है, तो आज इसे हटाने की ज़रूरत नहीं है। अगली बार Angular upgrade करते समय या Rive file का कोई नया feature अपनाते समय migration की योजना बनाएँ। इनमें मुख्य है Data Binding: Rive ने view models के पक्ष में स्टेट मशीन इनपुट को deprecate कर दिया है, और Data Binding से पहले लिखा गया wrapper इसे उपलब्ध नहीं कराएगा।

Grandgular project का अपना पुराना package, @grandgular/rive-angular, भी अलग-अलग -webgl2 और -canvas packages के पक्ष में deprecate हो चुका है। अगर आप wrapper चुनते हैं, तो मौजूदा नाम install करें:

npm install @grandgular/rive-angular-webgl2 @rive-app/webgl2
# or
npm install @grandgular/rive-angular-canvas @rive-app/canvas

इसकी README में न्यूनतम ज़रूरत Angular 18.0.0 और TypeScript 5.4 बताई गई है। templates लिखने से पहले मौजूदा component और input नामों के लिए README देखें।

Angular component में @rive-app/canvas को सीधे कैसे इस्तेमाल करें?

npm install @rive-app/canvas से रनटाइम install करें, character.riv को public/ में रखें (पुराने project layouts में src/assets/ में), और एक standalone component बनाएँ:

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() उन C++ objects को free करता है जो रनटाइम ने WebAssembly में allocate किए थे, इसलिए destroy पर इसे हमेशा कॉल करें।

mood नंबर को signal से कैसे चलाएँ?

ऊपर वाला component यह पहले से करता है: mood एक signal input है, और constructor में मौजूद effect() हर बदलाव पर दोबारा चलता है और value को bound view model में लिखता है। क्योंकि effect सिर्फ़ signal पढ़ता है और Rive रनटाइम में लिखता है, यह न change detection ट्रिगर करता है, न कोई loop बनाता है।

हो सकता है effect फ़ाइल load होने से पहले ही चल जाए। setter जाँचता है कि viewModelInstance मौजूद है, और फ़ाइल तैयार होते ही onLoad मौजूदा value लागू कर देता है, इसलिए पहला मूड कभी नहीं खोता।

parent component में मूड को ऐप state से निकालें और वन-शॉट रिएक्शन (एक बार चलने वाला रिएक्शन) के लिए fire() कॉल करें:

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
  }
}
Rive स्टेट मशीन का डायग्राम: एक entry स्टेट, मूड नंबर से चुने जाने वाले दस लूप स्टेट, और वन-शॉट ट्रिगर स्टेट जो मौजूदा लूप पर लौटते हैं
Charmotion .riv कैसे जुड़ा है: मूड नंबर लूप चुनता है, ट्रिगर किसी भी स्टेट से वन-शॉट चलाते हैं।

लगातार चलने वाली स्टेट (idle, loading, listening) के लिए मूड रखें और events (success, error) के लिए ट्रिगर। यह बँटवारा state के लिए signals और events के लिए methods पर साफ़-साफ़ बैठता है।

Angular server rendering में SSR errors से कैसे बचें?

Angular का server renderer आपके components को Node.js में चलाता है, जहाँ न canvas है, न window, और न Rive रनटाइम चलाने का कोई तरीका। server पर instance बनाने से या तो error आता है या बेकार काम होता है। ngAfterViewInit में isPlatformBrowser(this.platformId) वाली जाँच server पर पहले ही return कर देती है, इसलिए वहाँ सिर्फ़ canvas markup render होता है और एनिमेशन hydration के बाद शुरू होता है।

Angular afterNextRender भी देता है, जिसके बारे में Angular के docs कहते हैं कि यह सिर्फ़ browser में, rendering के बाद चलता है। यह platform check का एक साफ़-सुथरा विकल्प है:

constructor() {
  afterNextRender(() => this.start()); // never runs on the server
  effect(() => this.setMood(this.mood()));
}

दोनों में से कोई भी तरीका अपनाएँ, ngOnDestroy वाला cleanup बनाए रखें; optional chaining के साथ server पर यह कुछ नहीं करता। अगर कभी server build में import ही परेशानी पैदा करे, तो browser-only callback के अंदर await import("@rive-app/canvas") से रनटाइम को lazily load करें।

Charmotion की फ़ाइलें ठीक इसी wiring के लिए बनी हैं: आपके कैरेक्टर या लोगो से 17 एनिमेशन वाली एक .riv फ़ाइल, Character व्यू मॉडल जिसमें 0 से 9 तक का mood नंबर और छह ट्रिगर हैं, साथ ही पुराने wrappers के लिए legacy CharacterStateMachine इनपुट। सेट में web, React और SwiftUI का example code भी शामिल है।

Canvas या WebGL2 package: कौन सा चुनें?

दोनों packages एक ही Rive class और options देते हैं, इसलिए बदलना सिर्फ़ एक लाइन का import बदलाव है।

  • @rive-app/canvas browser के 2D canvas API से draw करता है। Rive इसे उन पेजों के लिए सुझाता है जिन पर एक साथ कई एनिमेशन दिखते हैं, क्योंकि यह browser की WebGL contexts की सीमा से बंधा नहीं है।
  • @rive-app/webgl2 WebGL2 पर Rive Renderer इस्तेमाल करता है और पूरा feature set सपोर्ट करता है, उन effects समेत जिन्हें 2D canvas draw नहीं कर सकता। Rive ज़्यादातर single-animation पेजों के लिए इसे सुझाता है।

ऐसे डैशबोर्ड के लिए जिसमें कई cards में छोटा मैस्कॉट हो, canvas चुनें। advanced effects वाले एक बड़े हीरो कैरेक्टर के लिए WebGL2 चुनें। Grandgular wrapper भी इसी चुनाव के हिसाब से दोनों के लिए अलग package देता है।

जो भी चुनें, इनमें से सिर्फ़ एक ही install करें। दोनों अपना WASM bundle करते हैं, और दो रनटाइम ship करने से बिना किसी फ़ायदे के डाउनलोड दोगुना हो जाता है। अगर बाद में बदलते हैं, तो self-hosted wasm path भी बदल जाता है, इसलिए उसी समय किसी भी RuntimeLoader.setWasmUrl() import को अपडेट करें।

बिना wrapper के Angular ऐप में Rive कैरेक्टर कैसे जोड़ें

  1. रनटाइम इंस्टॉल करें. npm install @rive-app/canvas चलाएँ और .riv फ़ाइल को public/ में जोड़ें।
  2. एक standalone component बनाएँ. template reference वाला एक canvas और एक mood signal input जोड़ें।
  3. सिर्फ़ browser में शुरू करें. new Rive({ stateMachine: "CharacterViewModelMachine", autoBind: true, ... }) को ngAfterViewInit में isPlatformBrowser की जाँच के साथ, या afterNextRender में बनाएँ।
  4. signal को sync करें. effect() की मदद से मूड को viewModelInstance.number("mood") में लिखें।
  5. ट्रिगर expose करें. एक fire(name) method जोड़ें जो trigger(name).trigger() को कॉल करे।
  6. Cleanup करें. ngOnDestroy में this.r?.cleanup() कॉल करें।

अक्सर पूछे जाने वाले सवाल

क्या मुझे Grandgular Rive wrapper इस्तेमाल करना चाहिए?

यह वही community wrapper है जिसे Rive के docs अभी सूचीबद्ध करते हैं। इसके लिए Angular 18 या उसके बाद का वर्ज़न चाहिए और इस पर सक्रिय रूप से काम चल रहा है, लेकिन अभी कम ही प्रोजेक्ट इसका इस्तेमाल करते हैं। प्रोटोटाइप के लिए यह ठीक है। जिस प्रोडक्ट को आप सालों तक मेंटेन करेंगे, उसके लिए plain web runtime बेहतर है, क्योंकि इससे आप किसी छोटे third-party package पर निर्भर नहीं रहते।

क्या Rive zoneless Angular के साथ काम करता है?

हाँ। Rive रनटाइम requestAnimationFrame के ज़रिए अपना render loop खुद चलाता है और Zone.js या change detection पर निर्भर नहीं है। Signals और effect() वैल्यू सीधे रनटाइम में भेजते हैं, इसलिए ऊपर वाला component zones के साथ या उनके बिना, एक जैसा काम करता है। NgZone.runOutsideAngular wrapper की ज़रूरत नहीं है।

Angular प्रोजेक्ट में .riv फ़ाइल कहाँ रखें?

मौजूदा Angular प्रोजेक्ट में इसे public/ में रखें और character.riv के रूप में लोड करें। पुराने layouts src/assets/ से फ़ाइलें serve करते हैं, इसलिए वहाँ path assets/character.riv होगा। दोनों में से जो भी फ़ोल्डर हो, उसका build assets configuration में होना ज़रूरी है। नए प्रोजेक्ट में यह डिफ़ॉल्ट रूप से सेट रहता है।

इस गाइड से झटपट जवाब

स्रोत

संबंधित गाइड