Rive로 인터랙티브 애니메이션 만들기: 상태 머신, 웹·React·Flutter 통합, 런타임 API

이 글의 핵심

Rive는 벡터 그래픽, 애니메이션, 상태 머신을 한 .riv 파일에 담아 코드에서 값만 바꿔 디자이너가 설계한 인터랙션을 재현하게 해 주는 도구입니다. 이 글은 에디터 워크플로와 상태 머신 설계, 웹·React·Flutter 통합을 다루면서, 최근 런타임에서 상태 머신 입력 대신 권장되는 데이터 바인딩(View Model), 네이티브 C++ 런타임으로 바뀐 Flutter 0.14의 API 변화, 이름 계약이 깨졌을 때 조용히 실패하는 문제와 성능 튜닝, Lottie와의 선택 기준을 정리합니다.

Rive는 벡터 에셋을 만들고, 상태 머신(State Machine)으로 런타임에 제어할 수 있는 인터랙티브 애니메이션을 만드는 도구입니다. After Effects에서 만든 애니메이션을 JSON으로 내보내 재생하는 Lottie와 달리, Rive는 에디터와 런타임, 상태 모델이 함께 설계되어 있어 UI 컨트롤, 캐릭터, 마이크로 인터랙션에 잘 맞습니다. 이 글은 핵심 개념과 에디터 작업 흐름, 상태 머신 설계를 먼저 보고, 웹·React·Flutter 통합과 런타임 API, 성능, Lottie와의 선택 기준을 다룹니다.


Rive를 쓰는 이유

영상처럼 한 방향으로만 재생되는 에셋은 버튼 호버, 토글, 로딩 상태, 온보딩 튜토리얼처럼 상태가 자주 바뀌는 화면에 넣기 어렵습니다. Rive는 같은 파일(.riv) 안에 그래픽과 논리(상태 전이)를 함께 담아, 개발자가 코드에서 입력만 바꿔도 디자이너가 의도한 전환이 재현되도록 설계되어 있습니다.

대시보드 위젯, 온보딩 일러스트, 로딩·성공 피드백, 커스텀 토글과 슬라이더, 뼈대 리깅이 들어간 마스코트 캐릭터처럼 상태에 따라 반응해야 하는 그래픽에 잘 맞습니다. 반대로 사진이나 비디오 기반 모션 그래픽을 대체하는 용도에는 맞지 않고, 이미 Lottie 파이프라인이 잘 돌아가는 팀이라면 단순 재생 애니메이션까지 옮길 이유는 크지 않습니다.


핵심 개념

아트보드(Artboard)와 캔버스

아트보드는 작업 단위의 루트입니다. 해상도·프레임은 아트보드에 묶이며, 런타임에서는 보통 한 번에 하나의 아트보드를 선택해 재생합니다. 여러 화면 변형(라이트/다크, A/B)을 한 파일에 넣을 때는 아트보드를 나누거나, 상태 머신으로 같은 아트보드 안에서 전환하는 방식 중 하나를 선택합니다.

벡터 셰이프와 계층

Rive는 벡터 기반입니다. 경로, 페인트, 그라디언트, 마스크 등이 계층 트리로 관리됩니다. 뼈대(Bones)와 가중치(Weights)로 캐릭터 리깅을 할 수 있어, 단순 UI 아이콘부터 관절이 있는 일러스트까지 같은 도구로 다룰 수 있습니다.

애니메이션(Animations)

타임라인 애니메이션은 키프레임으로 프로퍼티(위치, 회전, 색, 모프 등)를 기록합니다. 런타임에서는 단순 재생·루프·믹스가 가능합니다. 인터랙션의 주 상태 전이까지 타임라인만으로 처리하려면 조합이 기하급수로 늘어나기 쉬우므로, 실무에서는 상태 머신과 역할을 나누는 것이 일반적입니다.

상태 머신(State Machine)

상태 머신은 이름 그대로 상태(State)와 전이(Transition)로 동작을 정의합니다. 각 상태는 하나 이상의 애니메이션 클립(또는 믹스)과 연결되고, 입력(Input)—예: hover, pressed, progress—에 따라 어떤 전이가 발동할지 규칙으로 기술합니다.

런타임에서는 디자이너가 노출해 둔 입력 이름에 맞춰 앱 코드에서 값을 설정합니다. 이 계약이 명확하면 디자인 변경이 코드에 미치는 영향을 최소화할 수 있습니다.

입력(Input) 타입

자주 쓰는 입력 유형은 다음과 같습니다.

유형용도 예시
Boolean호버, 선택, 켜짐/꺼짐
Number진행률, 슬라이더, 스크롤 연동
Trigger한 번 실행되는 펄스(성공 체크 등)

Trigger는 내부적으로 “한 프레임/짧은 구간”에 맞춰 소비되는 패턴으로 사용됩니다. 연속 값이 필요하면 Number를 사용합니다.

데이터 바인딩(View Model)

최근 Rive에서 앱과 애니메이션을 연결하는 권장 방식은 입력이 아니라 데이터 바인딩입니다. 에디터에서 View Model(속성 묶음: 숫자, 불리언, 문자열, 색, 트리거, 열거형, 목록 등)을 정의하고, 그 속성을 도형의 위치·색·텍스트나 상태 머신 전이 조건에 연결합니다. 런타임에서는 View Model 인스턴스를 아트보드에 바인딩하고 속성 값을 바꿉니다.

입력과 비교하면 차이는 세 가지입니다. 첫째, 문자열·색·목록처럼 입력으로는 넘기기 어려웠던 데이터를 그대로 넘길 수 있어서, 예전에는 텍스트 런을 이름으로 찾아 바꾸던 작업이 속성 하나로 끝납니다. 둘째, 속성 변경을 앱 쪽에서 구독할 수 있어 애니메이션 안에서 일어난 일을 앱이 알 수 있습니다. 셋째, 같은 View Model을 여러 아트보드가 공유할 수 있습니다. Rive 런타임은 상태 머신 입력과 텍스트 런에 접근하는 메서드를 deprecated로 표시하고 데이터 바인딩으로 옮기라고 안내하고 있으므로, 새 파일은 처음부터 View Model로 설계하는 편이 낫습니다. 아래 웹·React·Flutter 0.13 예제 중 입력(useStateMachineInput, SMIBool)을 쓰는 코드는 기존 파일을 다루는 경우를 위한 것이고, 데이터 바인딩 코드는 Flutter 0.14 예제와 런타임 API 절의 “데이터 바인딩 (웹)“에 정리했습니다.


Rive Editor 사용법

설치와 프로젝트

Rive는 데스크톱 에디터와 브라우저 에디터를 제공합니다. 계정을 만들고 팀·프로젝트를 나누면 버전 관리와 공유가 수월합니다. .riv는 바이너리 포맷이므로 Git에는 원본과 함께 “어떤 장면이 바뀌었는지”를 이슈나 릴리즈 노트로 남기는 것이 협업에 도움이 됩니다.

기본 워크플로

  1. 아트보드 크기와 이름을 정합니다.
  2. 벡터로 셰이프를 그리거나 SVG를 가져옵니다.
  3. 타임라인 애니메이션으로 기본 모션(대기, 호버, 클릭 반응 등)을 만듭니다.
  4. State Machine을 추가하고 상태와 전이를 연결합니다.
  5. 입력(또는 View Model 속성)을 정의하고 전이 조건에 연결합니다.
  6. 에디터의 미리보기에서 입력을 조작해 전이가 의도대로 일어나는지 확인합니다.

디자이너와 개발자가 맞춰야 할 것

런타임 코드는 아트보드, 상태 머신, 입력, View Model 속성을 모두 문자열 이름으로 찾습니다. 그래서 이 이름과 타입(예: isOpen은 Boolean, progress는 0~1 Number, onSuccess는 Trigger)을 문서로 남기고, 바꿀 때는 개발자와 함께 바꾸는 규칙이 필요합니다. 쓰지 않는 레이어와 과도한 클립 중첩도 런타임 비용이 되므로 넘기기 전에 정리하는 편이 좋습니다.


State Machine과 인터랙션

상태 설계

상태는 사용자에게 보이는 모드와 맞추는 것이 디버깅에 유리합니다. 예: idle, hover, pressed, loading, success, error. 전이에는 조건(입력 값), 이징, 믹스 딜레이 등을 걸 수 있습니다.

인터랙션 패턴

버튼은 hover와 pressed 두 Boolean으로 상태를 나누는 것이 일반적이고, 토글은 isOn Boolean 하나로 두 상태를 오갑니다. 프로그레스 링은 0~1 Number 입력을 진행 애니메이션의 위치에 매핑하고, 성공 피드백은 complete Trigger를 발동시킨 뒤 완료 클립이 끝나면 idle로 돌아오는 전이를 둡니다.

코드와의 경계

상태 머신에 비즈니스 규칙 전부를 넣기보다, 표현과 피드백에 가깝게 유지하는 편이 낫습니다. 예를 들어 “결제 성공 여부”는 앱 상태에 두며, 성공 시에만 Trigger를 쏘는 식으로 단방향 데이터 흐름을 유지하면 테스트가 쉬워집니다.


웹 통합 (Canvas / WebGL)

웹 런타임은 두 가지 패키지로 나뉩니다. @rive-app/canvas는 브라우저의 Canvas 2D API로 그리고 패키지가 작습니다. @rive-app/webgl2는 WebGL2 위에서 Rive Renderer로 그리며, 복잡한 장면이나 페더링 같은 최신 효과가 필요할 때 씁니다. 단순한 UI 애니메이션은 Canvas로 충분한 경우가 많고, 실제 선택은 대상 기기에서 프로파일링해 정하는 것이 좋습니다.

패키지 설치

npm install @rive-app/canvas
# 또는
npm install @rive-app/webgl2

최소 예시 (Canvas)

고수준 Rive 클래스는 .riv를 내려받아 파싱하고, requestAnimationFrame 루프에서 애니메이션을 진행·렌더링하는 일까지 내부에서 처리합니다. 개발자는 캔버스와 파일 경로, 레이아웃만 넘기면 됩니다.

import { Rive, Layout, Fit, Alignment } from '@rive-app/canvas';

const canvas = document.getElementById('rive-canvas') as HTMLCanvasElement;

const rive = new Rive({
  canvas,
  src: '/animations/ui-toggle.riv',
  autoplay: true,
  layout: new Layout({
    fit: Fit.Contain,
    alignment: Alignment.Center,
  }),
  onLoad: () => {
    rive.resizeDrawingSurfaceToCanvas();
  },
});

// 뷰가 사라질 때
// rive.cleanup();

resizeDrawingSurfaceToCanvas()는 CSS로 정해진 캔버스 크기에 devicePixelRatio를 곱해 드로잉 버퍼 크기를 맞추므로, 고해상도 화면에서 흐려지는 것을 막습니다. 캔버스 크기가 바뀔 수 있다면 ResizeObserver나 resize 이벤트에서 다시 호출합니다. 페이지나 컴포넌트를 떠날 때는 cleanup()으로 렌더 루프와 WASM 쪽 리소스를 정리해야 메모리가 쌓이지 않습니다.


React 통합

React에서는 @rive-app/react-canvas(또는 @rive-app/react-webgl2)의 훅으로 감쌉니다. 상태 머신 입력은 useStateMachineInput 훅이 돌려주는 핸들로 갱신합니다.

설치

npm install @rive-app/react-canvas

컴포넌트 예시

import { useState } from 'react';
import { useRive, useStateMachineInput } from '@rive-app/react-canvas';

type RiveToggleProps = {
  src: string;
  stateMachineName?: string;
};

export function RiveToggle({ src, stateMachineName = 'State Machine 1' }: RiveToggleProps) {
  const [on, setOn] = useState(false);

  const { RiveComponent, rive } = useRive({
    src,
    stateMachines: stateMachineName,
    autoplay: true,
  });

  const isOnInput = useStateMachineInput(rive, stateMachineName, 'isOn', on);

  const toggle = () => {
    const next = !on;
    setOn(next);
    if (isOnInput) {
      isOnInput.value = next;
    }
  };

  return (
    <button type="button" onClick={toggle} aria-pressed={on}>
      <RiveComponent style={{ width: 320, height: 180 }} />
    </button>
  );
}

설명: useRive로 캔버스를 렌더링하는 RiveComponent를 얻습니다. useStateMachineInput은 상태 머신 이름과 입력 이름이 에디터와 일치해야 합니다. React state와 Rive 입력을 동기화할 때는 “한쪽이 단일 진실 공급원”이 되도록 정하며, 이중 갱신을 피합니다.

Rive를 쓰면서 가장 자주 겪는 문제가 바로 이 이름 계약입니다. 디자이너가 에디터에서 입력 이름을 isOn에서 on으로 바꾸거나 상태 머신 이름을 정리해 새 .riv를 넘겨주면, useStateMachineInput은 에러 없이 null을 돌려주고 애니메이션은 그냥 반응하지 않습니다. 콘솔에도 아무것도 안 찍혀서 “코드는 안 건드렸는데 토글이 멈췄다”는 식으로 발견됩니다. 이를 막으려면 입력 핸들이 null이면 개발 모드에서 경고를 찍는 작은 래퍼를 두고, .riv 파일을 교체하는 PR에는 입력·View Model 속성 이름 목록을 함께 적는 규칙을 두는 것이 효과적입니다. rive.stateMachineInputs('State Machine 1')로 실제 입력 목록을 출력해 보면 이름 불일치를 바로 찾을 수 있습니다.

Next.js 등 SSR 환경

Rive는 캔버스와 WASM 같은 브라우저 API에 의존하므로 클라이언트에서만 실행되어야 합니다. Next.js App Router에서는 Rive를 쓰는 컴포넌트에 'use client'를 붙이고, 서버 렌더링 자체를 건너뛰려면 클라이언트 컴포넌트 안에서 dynamic(() => import('./RiveToggle'), { ssr: false })로 불러옵니다. ssr: false는 서버 컴포넌트에서는 쓸 수 없습니다.


Flutter 통합

Flutter에서는 공식 패키지 rive를 사용합니다. 여기서 주의할 점은 0.14에서 런타임이 통째로 바뀌었다는 것입니다. 0.13까지는 Dart로 구현된 런타임이었고 RiveAnimation 위젯과 StateMachineController로 입력을 제어했습니다. 0.14부터는 웹·iOS·Android와 같은 네이티브 C++ 런타임과 Rive Renderer를 쓰도록 교체되면서 API가 호환되지 않게 바뀌었고, 대신 데이터 바인딩·레이아웃·스크롤·벡터 페더링 같은 최신 기능을 Flutter에서도 쓸 수 있게 되었습니다. 인터넷 예제 대부분이 0.13 문법이라, 복사한 코드가 컴파일되지 않으면 먼저 pubspec.lock의 버전을 확인하세요.

0.14 이후 (권장)

dependencies:
  rive: ^0.14.0
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await RiveNative.init(); // 0.14부터 필요
  runApp(const MaterialApp(home: RewardsView()));
}

class RewardsView extends StatefulWidget {
  const RewardsView({super.key});
  @override
  State<RewardsView> createState() => _RewardsViewState();
}

class _RewardsViewState extends State<RewardsView> {
  File? _file;
  RiveWidgetController? _controller;
  ViewModelInstance? _vmi;

  @override
  void initState() {
    super.initState();
    _load();
  }

  Future<void> _load() async {
    final file = await File.asset('assets/rewards.riv', riveFactory: Factory.rive);
    if (file == null) return;
    final controller = RiveWidgetController(file);
    final vmi = controller.dataBind(DataBind.auto()); // 기본 View Model + 기본 인스턴스
    setState(() {
      _file = file;
      _controller = controller;
      _vmi = vmi;
    });
  }

  void _onEarned() {
    _vmi?.number('coins')?.value = 120;
    _vmi?.trigger('celebrate')?.fire();
  }

  @override
  void dispose() {
    _vmi?.dispose();
    _controller?.dispose();
    _file?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final controller = _controller;
    if (controller == null) return const SizedBox.shrink();
    return GestureDetector(
      onTap: _onEarned,
      child: RiveWidget(controller: controller, fit: Fit.contain),
    );
  }
}

파일·컨트롤러·View Model 인스턴스를 각각 dispose해야 네이티브 리소스가 해제됩니다. Dart 런타임 시절에는 GC에 맡겨도 크게 티가 나지 않았지만, 네이티브 런타임에서는 누락이 메모리 증가로 바로 이어지므로 화면을 오가는 앱이라면 꼭 확인하세요. 값 변경은 다음 프레임에 상태 머신이 진행될 때 반영됩니다. 정확한 생성자 인자(fit 등)는 버전마다 조금씩 바뀌고 있으니 pub.dev의 rive 문서를 기준으로 맞추세요.

0.13 이하 (기존 코드)

기존 앱을 유지보수하는 경우의 코드입니다. pubspec.yaml에 에셋 경로를 등록한 뒤, RiveAnimation.asset으로 로드하고 onInit에서 상태 머신을 붙입니다.

import 'package:flutter/material.dart';
import 'package:rive/rive.dart';

class RiveDemo extends StatefulWidget {
  const RiveDemo({super.key});

  @override
  State<RiveDemo> createState() => _RiveDemoState();
}

class _RiveDemoState extends State<RiveDemo> {
  StateMachineController? _controller;
  SMIBool? _hover;

  void _onRiveInit(Artboard artboard) {
    final controller =
        StateMachineController.fromArtboard(artboard, 'State Machine 1');
    if (controller == null) return;
    artboard.addController(controller);
    _hover = controller.findInput<bool>('hover') as SMIBool?;
    _controller = controller;
  }

  @override
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTapDown: (_) => _hover?.value = true,
      onTapUp: (_) => _hover?.value = false,
      child: RiveAnimation.asset(
        'assets/ui.riv',
        onInit: _onRiveInit,
        fit: BoxFit.contain,
      ),
    );
  }
}

설명: StateMachineController.fromArtboard로 에디터에서 지정한 상태 머신 이름을 연결합니다. findInput<bool> 등으로 입력 핸들을 얻어 값을 바꿉니다. 이 API는 0.14에서 제거·변경되었으므로, 새 기능이 필요해지면 위의 0.14 방식으로 옮겨야 합니다. 위젯이 dispose될 때 컨트롤러를 dispose하지 않으면 리소스가 남을 수 있습니다.


런타임 API

플랫폼별 이름은 다르지만 개념은 공통입니다.

로드, 재생, 입력

어느 런타임이든 흐름은 같습니다. URL이나 바이트 버퍼에서 .riv를 읽고, 한 파일에 아트보드가 여러 개면 이름으로 하나를 고릅니다. 타임라인 애니메이션만 재생할 때는 play 계열 API를 쓰고, 인터랙션이 필요하면 상태 머신 이름으로 인스턴스를 만든 뒤 입력 핸들을 얻습니다. Boolean과 Number 입력은 .value에 값을 넣고, Trigger 입력은 fire()를 호출합니다.

이벤트

Rive 이벤트(Events)를 지원하는 런타임에서는 애니메이션의 특정 시점이나 전이에서 이벤트를 발생시킬 수 있습니다. 에디터에서 이벤트를 쏘고, 앱에서 구독하는 방식으로 애니메이션과 사운드·햅틱을 동기화할 수 있습니다.

레이아웃

Fit, Alignment로 비율 유지·크롭·확대를 제어합니다. 반응형 UI에서는 캔버스/위젯 크기 변경 시 resizeDrawingSurfaceToCanvas(웹) 등으로 드로잉 버퍼를 맞춥니다. 에디터에서 레이아웃(Layouts) 기능으로 만든 아트보드는 Fit.Layout을 쓰면 캔버스 크기에 맞춰 내부 요소가 재배치됩니다.

데이터 바인딩 (웹)

웹 런타임에서 데이터 바인딩을 쓰는 가장 짧은 방법은 autoBind: true로 기본 View Model 인스턴스를 붙인 뒤 viewModelInstance에서 속성을 꺼내는 것입니다.

import { Rive } from '@rive-app/canvas';

const r = new Rive({
  canvas,
  src: '/animations/rewards.riv',
  stateMachines: 'State Machine 1',
  autoplay: true,
  autoBind: true,
  onLoad: () => {
    r.resizeDrawingSurfaceToCanvas();
    const vmi = r.viewModelInstance;
    if (!vmi) return; // 파일에 View Model이 없음

    vmi.number('coins')!.value = 120;
    vmi.string('userName')!.value = '게스트';
    vmi.trigger('celebrate')!.fire();

    // 애니메이션 안에서 바뀐 값을 앱이 구독
    vmi.boolean('isClaimed')?.on((e) => console.log('claimed:', e.data));
  },
});

속성 이름을 잘못 쓰면 역시 null이 돌아오므로, React 절에서 본 입력 이름과 같은 방식의 이름 관리가 필요합니다. React 런타임에도 View Model과 속성을 다루는 훅이 제공되니, 사용하는 @rive-app/react-canvas 버전의 문서를 확인하세요.


성능 최적화

비용은 에셋 쪽과 런타임 쪽에서 모두 생깁니다. 에셋 쪽에서는 레이어와 클립 마스크 수가 그리기 비용을 결정하고, 블러나 겹겹이 쌓인 마스크는 저사양 모바일 기기에서 특히 무겁습니다. 대상 기기가 저사양이라면 디자이너와 함께 이런 효과를 단순화하는 것이 가장 효과가 큽니다.

런타임 쪽에서는 동시에 돌아가는 인스턴스 수가 중요합니다. 인스턴스마다 렌더 루프와 캔버스가 따로 있으므로, 목록의 각 행에 Rive를 넣는다면 화면에 보이는 것만 만들도록 가상 스크롤과 함께 쓰고, 화면 밖으로 나간 인스턴스는 pause()하거나 정리합니다. SPA에서 화면을 떠날 때 cleanup()(웹)이나 dispose()(Flutter)를 빠뜨리면 이전 인스턴스가 메모리에 남습니다. 파일이 크다면 아래 FAQ처럼 이미지·폰트를 외부 에셋으로 빼고, 첫 화면에 필요 없는 애니메이션은 지연 로드합니다.


Lottie vs Rive

구분LottieRive
출발점After Effects 워크플로 중심전용 에디터 + 런타임 일체
상태·입력기본은 타임라인 재생. dotLottie 포맷과 일부 플레이어가 상태 머신을 추가로 지원상태 머신·입력·데이터 바인딩이 기본
파일JSON(텍스트), 압축 패키지인 .lottie.riv(바이너리)
강점모션 디자이너 풀 넓음, 단순 재생 가벼움인터랙티브 UI·게임성 피드백
주의복잡한 AE 이펙트는 지원 제한팀 학습·파이프라인 전환 비용

선택 가이드: “재생만 하면 된다”면 Lottie가 단순합니다. “사용자 입력과 상태가 애니메이션과 강하게 엮인다”면 Rive가 구조적으로 유리합니다. 둘 다 쓰는 것도 가능하며, 역할을 나누는 기준(마케팅 루프는 Lottie, 핵심 UI 마이크로 인터랙션은 Rive)을 팀 규약으로 정하면 혼선이 줄어듭니다.


트러블슈팅

  • 입력이 반응하지 않음: 상태 머신 이름·입력 철자가 코드와 완전히 동일한지 확인합니다. 공백·대소문자까지 포함합니다. 기본 이름 State Machine 1을 그대로 쓰다가 디자이너가 이름을 바꾸는 경우가 흔합니다.
  • 새 기능이 적용되지 않음: 에디터에서 새로 추가된 기능(데이터 바인딩, 레이아웃, 페더링 등)을 쓴 .riv를 오래된 런타임으로 재생하면 해당 부분이 무시되거나 다르게 보입니다. 에디터 버전과 런타임 패키지 버전을 함께 올리세요.
  • Canvas와 WebGL2에서 모양이 다름: @rive-app/canvas는 브라우저 Canvas 2D로 그리고, @rive-app/webgl2는 Rive Renderer로 그립니다. 페더링 같은 일부 효과는 Rive Renderer에서만 제대로 나오므로, 디자이너가 확인한 화면과 다르면 백엔드부터 맞춰 보세요.
  • 캔버스가 비어 보임: 로드 URL이 잘못되었거나, 캔버스 크기가 0입니다. 개발자 도구 네트워크 탭에서 .riv 요청 상태를 확인합니다.
  • 흐릿하게 보임: DPR을 반영한 리사이즈가 빠졌을 수 있습니다. resizeDrawingSurfaceToCanvas 호출 여부를 점검합니다.
  • 메모리 증가: 라우트 이동 후 이전 인스턴스가 정리되지 않았을 수 있습니다. cleanup/dispose를 연결합니다.

자주 묻는 질문 (FAQ)

Q. .riv 파일이 커서 초기 로딩이 느립니다.

A. 파일 크기의 대부분은 보통 임베드된 이미지와 폰트입니다. 에디터의 에셋 설정에서 이미지·폰트를 파일 밖에서 불러오는(참조) 방식으로 바꾸면 .riv는 작아지고, 에셋은 CDN에서 따로 캐시됩니다. 화면에 바로 보이지 않는 애니메이션은 뷰포트에 들어올 때 로드하세요.

참고


같이 보면 좋은 글