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

Vue 3 और Nuxt में Rive: सेटअप, Data Binding, SSR

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

frameworks और no-code गाइड का कवर इलस्ट्रेशन: Vue 3 और Nuxt में Rive: Setup, Data Binding, SSR, साथ में Charmotion के डेमो कैरेक्टर

Rive का कोई आधिकारिक Vue runtime नहीं है, इसलिए Vue 3 और Nuxt ऐप सीधे web runtime @rive-app/canvas इस्तेमाल करते हैं। Rive instance को onMounted में autoBind: true के साथ बनाएँ, view model properties को watcher से सेट करें, और onUnmounted में cleanup() कॉल करें। Nuxt में component को सिर्फ़ client पर रेंडर करें, क्योंकि canvas और WASM को ब्राउज़र चाहिए।

मुख्य बातें

  • Vue के लिए कोई Rive पैकेज नहीं है; @rive-app/canvas या @rive-app/webgl2 किसी भी Vue 3 component में काम करता है।
  • instance को onMounted में बनाएँ, कभी भी setup के top level पर नहीं, और उसे ref() से बाहर रखें।
  • autoBind: true के साथ r.viewModelInstance.number("mood").value सेट करें और .trigger("success").trigger() कॉल करें।
  • WASM objects को फ़्री करने के लिए onUnmounted में r.cleanup() कॉल करें।
  • Nuxt में server-side errors से बचने के लिए .client.vue component या <ClientOnly> इस्तेमाल करें।

क्या Vue के लिए कोई आधिकारिक Rive पैकेज है?

नहीं। Rive web (JavaScript), React, Apple platforms, Android, Flutter और React Native समेत कई प्लेटफ़ॉर्म के लिए आधिकारिक runtime बनाए रखता है। उसके documentation में चार community runtime दिए गए हैं (Angular, C#, Qt और Compose Multiplatform के लिए RiveCMP), और इनमें से कोई भी Vue के लिए नहीं है। Rive का एक पुराना help page, जो Vue developers को web runtime इस्तेमाल करने की सलाह देता था, अब हटा दिया गया है, लेकिन वह सलाह आज भी लागू होती है।

यह कमी उतनी बड़ी नहीं है जितनी लगती है। web runtime किसी framework से बँधा नहीं है: उसे बस एक <canvas> element, एक .riv फ़ाइल और एक स्टेट मशीन का नाम चाहिए। Vue आपको ठीक वही hooks देता है जिनसे आप ये चीज़ें सही समय पर दे सकते हैं।

ज़रूरतपैकेजनोट्स
Vue या Nuxt में Rive@rive-app/canvas2D canvas renderer; WebGL context की कोई सीमा नहीं
एडवांस्ड इफ़ेक्ट के साथ Rive@rive-app/webgl2वही API, WebGL2 पर Rive Renderer
Vue में Lottie@lottiefiles/dotlottie-vueLottieFiles का आधिकारिक component
Nuxt सर्वर रेंडरिंग<ClientOnly> या .client.vueCanvas और WASM सिर्फ़ browser में चलते हैं

Rive के अनुसार @rive-app/canvas उन पेजों के लिए सही है जिनमें कई एनिमेटेड canvas हों, क्योंकि यह browser की WebGL context सीमा से बँधा नहीं है। अगर आपकी फ़ाइल ऐसे features इस्तेमाल करती है जिन्हें Rive Renderer चाहिए, तो @rive-app/webgl2 चुनें; import के अलावा नीचे का code बिल्कुल वही है।

onMounted में .riv को load और onUnmounted में cleanup कैसे करें?

npm install @rive-app/canvas से runtime install करें और फ़ाइल को public/ में रखें, जिसे Vite और Nuxt दोनों site root से serve करते हैं। फिर एक component बनाएँ जिसमें एक Rive instance हो:

<script setup>
import { watch, onMounted, onUnmounted, useTemplateRef } from "vue";
import { Rive, Layout, Fit } from "@rive-app/canvas";

const props = defineProps({ mood: { type: Number, default: 0 } });
const canvas = useTemplateRef("canvas");
let r = null; // plain variable: don't wrap the instance in ref()

function setMood(value) {
  const prop = r?.viewModelInstance?.number("mood");
  if (prop) prop.value = value;
}

function fire(name) {
  r?.viewModelInstance?.trigger(name)?.trigger();
}

onMounted(() => {
  r = new Rive({
    src: "/character.riv",
    canvas: canvas.value,
    stateMachine: "CharacterViewModelMachine",
    autoplay: true,
    autoBind: true,
    layout: new Layout({ fit: Fit.Contain }),
    onLoad: () => {
      r.resizeDrawingSurfaceToCanvas();
      setMood(props.mood);
    },
  });
});

watch(() => props.mood, setMood);
onUnmounted(() => r?.cleanup());
defineExpose({ fire });
</script>

<template>
  <canvas ref="canvas" width="320" height="320"
          role="img" aria-label="Animated mascot" />
</template>

Vue का onMounted तब चलता है जब component का DOM बन चुका होता है, इसलिए canvas ref सेट रहता है। useTemplateRef के लिए Vue 3.5 चाहिए; पुराने versions में const canvas = ref(null) इस्तेमाल करें। Rive instance को सामान्य variable या shallowRef में रखें: WASM पर आधारित object के चारों ओर deep reactive proxy overhead बढ़ाता है और runtime के अंदर identity checks तोड़ सकता है।

cleanup() जितना दिखता है उससे ज़्यादा ज़रूरी है। Rive के docs के अनुसार यह instance के पूरे जीवनकाल में बने low-level C++ objects को हटाता है। इसके बिना, मैस्कॉट को फिर से mount करने वाला हर route change मेमोरी लीक करता है।

Vue से मूड कैसे बदलें और ट्रिगर कैसे फ़ायर करें?

ऊपर वाला component पहले से दोनों काम करता है। props.mood पर लगा watch नई value को bound view model में लिखता है, और exposed fire() method एक वन-शॉट रिएक्शन (एक बार चलने वाला रिएक्शन) चलाता है। parent इसे किसी भी दूसरे component की तरह चलाता है:

<script setup>
import { ref, useTemplateRef } from "vue";
import CharacterRive from "./CharacterRive.vue";

const mascot = useTemplateRef("mascot");
const saving = ref(false);

async function save() {
  saving.value = true;               // mood 5: loading
  try {
    await api.save();
    mascot.value.fire("success");
  } catch {
    mascot.value.fire("error");
  } finally {
    saving.value = false;            // back to mood 0: idle
  }
}
</script>

<template>
  <CharacterRive ref="mascot" :mood="saving ? 5 : 0" />
  <button @click="save">Save</button>
</template>
Rive स्टेट मशीन का डायग्राम: एक entry स्टेट, मूड नंबर से चुने जाने वाले दस लूप स्टेट, और वन-शॉट ट्रिगर स्टेट जो मौजूदा लूप पर लौटते हैं
Charmotion .riv कैसे जुड़ा है: मूड नंबर लूप चुनता है, ट्रिगर किसी भी स्टेट से वन-शॉट चलाते हैं।

ऐप की state को ही source of truth रखें और मूड उसी से निकालें, जैसा :mood binding करती है। ट्रिगर events हैं, state नहीं, इसलिए वे prop के बजाय method से जाते हैं।

अगर कोई file view models की जगह पुराना input system इस्तेमाल करती है, तो यही pattern r.stateMachineInputs("CharacterStateMachine") के साथ काम करता है: input को नाम से ढूँढें, number पर .value सेट करें और ट्रिगर पर .fire() कॉल करें। state machine गाइड दोनों systems समझाती है।

Nuxt में SSR errors के बिना Rive कैसे इस्तेमाल करें?

Nuxt पेजों को पहले server पर रेंडर करता है, जहाँ न window है, न canvas और न Rive के लिए WebAssembly loader। onMounted server पर कभी नहीं चलता, इसलिए ऊपर वाला component ज़्यादातर पहले से सुरक्षित है। बचा हुआ जोखिम खुद import है: अगर कोई module import होते ही browser globals को छूता है, तो server bundle window is not defined error देता है।

Nuxt component को server rendering से बाहर रखने के दो तरीके देता है:

  • file का नाम CharacterRive.client.vue रखें। तब Nuxt इसे सिर्फ़ client पर रेंडर करता है, और यह server render में शामिल नहीं होता।
  • इसे <ClientOnly> में wrap करें। slot सिर्फ़ browser में रेंडर होता है, और server पर #fallback slot रेंडर होता है।
<ClientOnly>
  <CharacterRive :mood="2" />
  <template #fallback>
    <img src="/character-poster.png" width="320" height="320" alt="">
  </template>
</ClientOnly>

fallback image layout को स्थिर रखती है और crawlers व धीमे connections वाले users को कुछ न कुछ दिखाती है। अगर आप एक ही universal component रखना चाहते हैं, तो import को hook के अंदर ले जाएँ: async onMounted के अंदर const { Rive } = await import("@rive-app/canvas")। onUnmounted को setup में synchronously register करें, await के बाद नहीं, वरना Vue उसे component से नहीं जोड़ पाएगा।

Vite या Nuxt में rive.wasm को self-host कैसे करें?

डिफ़ॉल्ट रूप से web runtime पहली बार instance बनाने पर rive.wasm को unpkg से डाउनलोड करता है। यह सुविधाजनक है, लेकिन इससे एक third-party request जुड़ती है और offline या locked-down environments में यह fail हो जाता है। Rive के docs दिखाते हैं कि RuntimeLoader.setWasmUrl() से runtime को अपनी copy की ओर कैसे point करें, और ज़ोर देते हैं कि wasm file का version installed @rive-app package के version से मेल खाना चाहिए।

Vite के साथ (जिसे Nuxt इस्तेमाल करता है), ?url suffix file को आपके build में copy करता है और उसका hashed URL लौटाता है, इसलिए version हमेशा package.json से मेल खाता है:

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

इसे एक छोटे module में रखें जिसे Rive component import करता है, या Nuxt client plugin (plugins/rive.client.js) में। क्योंकि URL installed package से आता है, @rive-app/canvas को upgrade करने पर wasm अपने आप upgrade हो जाता है। rive.wasm को हाथ से public/ में copy न करें: अगले package update पर दोनों का sync बिगड़ जाएगा।

Self-hosting से आप उन पेजों पर फ़ाइल को preload भी कर सकते हैं जहाँ मैस्कॉट पेज के ऊपरी हिस्से (above the fold) में है, wasm URL के लिए <link rel="preload"> के साथ, ताकि यह आपके JavaScript के साथ-साथ डाउनलोड हो।

Vue मैस्कॉट के लिए Rive या Lottie?

दोनों Vue में काम करते हैं। चुनाव इस पर निर्भर करता है कि आपके code को एनिमेशन को यह बताना है या नहीं कि आगे क्या करना है।

  • Lottie linear clips के लिए सही है: success check, एम्प्टी स्टेट (जब स्क्रीन पर दिखाने को कुछ न हो) का illustration, spinner। LottieFiles @lottiefiles/dotlottie-vue publish करता है, जो <DotLottieVue autoplay loop src="…" /> रेंडर करता है और .lottie और .json फ़ाइलें चलाता है। मूड बदलने का मतलब है फ़ाइलें या segments बदलना।
  • Rive स्टेट वाले कैरेक्टर के लिए सही है। एक .riv में हर लूप और रिएक्शन होता है, state machine transitions संभालती है, और आपका component सिर्फ़ एक number लिखता है या ट्रिगर फ़ायर करता है।

अगर आपके पास अभी Rive file नहीं है, तो Charmotion आपके लिए एक बना देता है, कैरेक्टर या लोगो इमेज से, कुछ ही मिनटों में: एक ही .riv में 17 एनिमेशन, Character व्यू मॉडल के साथ (idle, happy, wave, thinking, sad, loading, listening, talking, sleeping और attention के लिए 0 से 9 तक का mood number, साथ में success, error, yes, no, enter और exit ट्रिगर)। फ़ाइल में पुराने CharacterStateMachine इनपुट भी रहते हैं, और सेट में उन जगहों के लिए MP4, GIF और ट्रांसपेरेंट WebM और MOV exports भी हैं जहाँ canvas फ़िट नहीं होता।

एक कैरेक्टर की दस लूपिंग स्टेट, हर एक 0 से 9 तक के मूड नंबर से चुनी जाती है
एक Charmotion Rive file की दस लूपिंग स्टेट, जिन्हें मूड नंबर इनपुट से चुना जाता है।

Vue 3 या Nuxt ऐप में Rive कैरेक्टर कैसे जोड़ें

  1. web runtime install करें. npm install @rive-app/canvas चलाएँ और .riv फ़ाइल को public/ में रखें।
  2. component बनाएँ. template ref के साथ एक canvas जोड़ें और onMounted में new Rive({ ..., stateMachine: "CharacterViewModelMachine", autoBind: true }) बनाएँ।
  3. मूड bind करें. mood prop को watch करें और r.viewModelInstance.number("mood").value सेट करें, और एक बार onLoad में भी।
  4. ट्रिगर expose करें. एक method expose करें जो success और error के लिए r.viewModelInstance.trigger(name).trigger() कॉल करे।
  5. Cleanup करें. onUnmounted में r.cleanup() कॉल करें।
  6. Nuxt SSR को संभालें. फ़ाइल का नाम .client.vue रखें या उसे poster image fallback के साथ <ClientOnly> में wrap करें।

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

क्या मैं Vue ऐप में @rive-app/react-canvas इस्तेमाल कर सकता हूँ?

नहीं। React पैकेज React hooks और rendering पर निर्भर है। Vue में @rive-app/canvas या @rive-app/webgl2 इस्तेमाल करें, ये framework-agnostic web runtime हैं जिन्हें React पैकेज खुद wrap करता है। फ़ाइलें लोड करने, view model bind करने और cleanup करने का API वही है जो Rive के web runtime पेजों पर documented है।

Rive import करने पर Nuxt "window is not defined" क्यों कहता है?

Import server rendering के दौरान चला, जहाँ browser globals मौजूद नहीं होते। Component का नाम बदलकर CharacterRive.client.vue रखें ताकि Nuxt उसे सिर्फ़ ब्राउज़र में render करे, उसे <ClientOnly> में wrap करें, या onMounted के अंदर @rive-app/canvas को dynamically import करें। इन तीनों में से कोई भी तरीका runtime को server से दूर रखता है।

क्या Rive instance को ref में रखना चाहिए?

इसे एक सामान्य variable या shallowRef में रखें। Deep ref() instance को reactive proxy में wrap कर देता है, जिससे overhead बढ़ता है और यह उसके अंदर के WASM-backed objects में दखल दे सकता है। आपके टेम्पलेट को instance सीधे पढ़ने की ज़रूरत कभी नहीं पड़ती, इसलिए इसे reactive होने की बिल्कुल ज़रूरत नहीं है।

क्या mood watcher फ़ाइल लोड होने से पहले चलता है?

चल सकता है, इसीलिए setter लिखने से पहले जाँचता है कि viewModelInstance मौजूद है। फिर फ़ाइल तैयार होते ही onLoad callback मौजूदा prop value लागू करता है, ताकि पहला मूड कभी न छूटे। इसके बाद हर prop बदलाव सीधे bound view model में जाता है।

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

स्रोत

संबंधित गाइड