【Rive】【React】使ってみたTipsまとめ

最近、個人的にRiveというアニメーションツールがお気に入りでいろいろ使ってみています。
使用してみた所感や、制作時に役立つかもしれないポイントなどをまとめてみようと思います。

※筆者は現状React環境でしか使用していません。

# Riveとは

Riveはコーディングなしでもインタラクティブなアニメーションを作成できるツールです。
WebやReact、React Native、iOS、android、Unity、Unreal Engineなどの様々なプラットフォームで使用できます。
しかもデータが驚くほど軽くて、4K解像度で作ったときも500KBくらいでした。すごい！

https://rive.app/

有名どころで言うとDuolingoのアニメーション制作に採用されています。

https://blog.duolingo.com/world-character-visemes/

使ってみたいよという方はまずはRiveの公式Youtubeでの学習がおすすめです。
短い動画でまとめられていてちょっとした空き時間に学習できます。

https://youtube.com/playlist?list=PLujDTZWVDSsFGonP9kzAnvryowW098-p3&si=IuOEB2-pnbKBne3-

# 制作したもの
個人で制作したものを紹介します。

## おみくじ
https://rive-omikuji2025.vercel.app/

astroとreactで制作したおみくじを引けるWebコンテンツです。
フロントはrivファイルを読み込んでいるだけで、他は全部Riveで制作しています。
おみくじのランダム出し分けもRiveだけでできちゃうんですね〜すごい！

## カーソルを追うウサギ
https://rive-rabbit-follow-cursor.vercel.app/

こちらもastroとreactで制作しました。
動かしたカーソルの方向にウサギが向いてくれます。
これもフロントはrivファイルを読み込んでいるだけです！

# 制作時のTipsまとめ

ここからは自主制作や案件で使用した際に得た知見いろいろを書いていこうと思います。
ツールの使い方などは上記の公式サイトやYoutubeに掲載されているので割愛します。

## rivデータが重すぎるときはアセットファイルの設定を見直そう
軽いと評判のRiveですが、最初はなぜか重すぎて「ぜんぜん軽くない...」となっていました。
探ってみるとアセットファイルが原因でした。

Assetsタブにはインポートした画像や使っているフォントなどを一覧で見ることができます。

![スクリーンショット 2025-01-09 14.41.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3733459/ce6a5633-f5cb-9b21-2d3d-9f90a6f13a20.png)

クリックすると、そのデータの詳細が表示されます。

![スクリーンショット 2025-01-09 14.40.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3733459/8d544498-20bb-0650-adb7-2b30e31d18a0.png)

ここで重要なのが**Export Options**の項目です。
ここのTypeがデフォルトだと**Embedded（埋め込み）** になっています。
ここを**Referenced（参照）** に変更することによってデータの軽量化が期待できます。

![スクリーンショット 2025-01-09 14.45.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3733459/027ca575-78d2-e58c-e66e-3be20f9435de.png)

また、フォントのExport OptionsにはTypeの他にIncludeという項目があります。
これはどの文字を含めるかを設定できます。
デフォルトだと**All Glyphs**となっていて、使っていない全ての文字データも含める設定になっています。
英文フォントならまだしも日本語フォントでこれになっているとめっちゃ重くなります。
**Glyphs used（使用している文字）** に設定することをおすすめします。

![スクリーンショット 2025-01-09 14.55.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3733459/61ec14e8-1fbc-289e-d4fd-acc732f006a6.png)

## Vite環境だとRuntimesのLayoutが使えない？
現在の最新バージョン（@rive-app/react-canvas 4.17.5）だと以下のエラーが出てしまいRuntimesのLayoutが使えませんでした。
エラーのとおりに修正してみてもだめでした...。
Layoutを使わずに表示させることは可能でした。

```
[ERROR] [vite] Named export 'Alignment' not found. The requested module '@rive-app/react-canvas' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from '@rive-app/react-canvas';
const {Layout, Fit, Alignment} = pkg;
```

同じエラーが出てしまっている方もいるみたいなので現状だとVite環境での使用ができないかもしれません。
解決策ご存知の方いらっしゃいましたら教えてくださると幸いです:bow:

https://github.com/rive-app/rive-react/issues/218

webpack環境だと問題なかったのでRiveを使う予定のプロジェクトはwebpackを検討してみてください。
（Rsbuildも大丈夫そうかも？まだ試してないです。）

## 特定のアニメーションが終わったら処理を走らせたい
RiveのRuntimesには再生されたとき、一時停止したとき、停止したときなどのそれぞれのイベントに対してcallback関数を設定できます。

https://rive.app/community/doc/animation-playback/docDKKxsr7ko#controlling-playback

https://rive.app/community/doc/rive-events/docbOnaeffgr

アニメーションが終了したときのイベントは残念ながら現状はありませんが、**ステータスが変更したとき（StateChange）** というイベントを利用することで実現することができます。

ステータスが変更されるのはアニメーションタイムラインが走り始めたときです。

以下のようにStateChangeにcallback関数を設定してみると

```javascript
const riveParams = {
  src: baseMapAnimation,
  artboard: 'artboard',
  stateMachines: 'StateMachine',
  layout: new Layout({
    fit: Fit.FitWidth,
    alignment: Alignment.Center,
  }),
  autoplay: true,
};
  
const { rive, RiveComponent } = useRive(riveParams);

useEffect(() => {
  if (!rive) return;
  rive.on(EventType.StateChange, (event) => {
    console.log('StateChange', event);
  });
}, [rive]);
```

dataにアニメーションがスタートしたタイムラインの名前が配列で入ってきます。

```javascript
StateChange
{
  data: [
    "Timeline_01",
    "Timeline_02"
  ],
  type: "statechange"
}
```

これを利用して、アニメーション終了用のタイムラインを作成することで終了時に処理を走らせることができます。

終了時に処理を走らせたい特定のタイムラインの最後のキーフレームを
アニメーション終了用タイムラインの最初のフレームに打っておくのがポイントです。

特定のタイムライン終了後にアニメーションがない場合、これをしておかないと画面が真っ白になってしまいます。

![スクリーンショット 2025-01-10 12.52.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3733459/a157641a-4fc1-c7fb-0d82-552f8fd8ec17.png)

あとはStateChange時にアニメーション終了用タイムラインの名前がdataにあるかどうかを判定すればOKです。

```javascript
useEffect(() => {
  if (!rive) return;
  
  rive.on(EventType.StateChange, (event) => {
    if (
      Array.isArray(event.data) &&
      event.data[0].startsWith('Timeline_exit_')
    ) {
      // アニメーション終了時に走らせたい処理
    }
  }, [rive]);
});
```

# さいごに
学習コストは高めですが、同じツール内でデザイナー、モーションデザイナー、エンジニアがシームレスに作業ができるので認識の齟齬も生まれないしとても良いな思っているので日本でも流行ってほしいです。