mcpField
R
rive-interactive

rive-interactive

github.com/freshtechbroデザイン・UI1.2k2.6k 回インストール49 週間前に更新
リポジトリ

rive-interactive について

Rive Interactive は、デザイナーや開発者向けの、ステートマシン駆動のベクターアニメーションを扱うスキルです。入力、イベント、ViewModel のバインディングにより、コードから状態やプロパティを制御できます。

主な機能

  1. 01ステートマシンと状態遷移
  2. 02Boolean・数値・トリガー入力
  3. 03ViewModel の双方向データバインディング
  4. 04カスタムアニメーションイベントとリスナー
  5. 05実行時のプロパティ制御

活用シーン

  1. 01複数の状態を持つ UI アニメーションの作成
  2. 02ホバーやクリックなどの入力への応答
  3. 03リアルタイムデータをアニメーション表示に連携
  4. 04React コンポーネントでのアニメーションイベント処理

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/freshtechbro/claudedesignskills --skill rive-interactive

インストール後、Claude Code で/rive-interactiveを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

assets/
README.md
references/
api_reference.md
scripts/
component_generator.py
viewmodel_builder.py

詳細

Rive Interactive - ステートマシンベースのベクターアニメーション

概要

Rive は、デザイナーが複雑なロジックとランタイムのインタラクティブ性を備えたインタラクティブなベクターアニメーションを作成できる、ステートマシンベースのアニメーションプラットフォームです。タイムラインのみを使用するアニメーションツール(Lottie など)とは異なり、Rive はステートマシン、入力処理、アプリケーションコードとアニメーション間の双方向データバインディングをサポートしています。

主な機能:

  • 複雑なインタラクティブロジックを実現するステートマシンシステム
  • 双方向データバインディングのための ViewModel API
  • 入力処理(ブール値、数値、トリガー入力)
  • アニメーションからコードへの通信に使うカスタムイベント
  • ランタイムプロパティの制御(色、文字列、数値、列挙型)
  • クロスプラットフォーム対応(Web、React、React Native、iOS、Android、Flutter)
  • ベクターグラフィックスによる小さなファイルサイズ

このスキルを使う場面:

  • 複雑な状態遷移を伴う UI アニメーションを作成する
  • インタラクティブなアニメーションコンポーネント(ボタン、トグル、ローダー)を構築する
  • 状態に応じてアニメーションするゲーム風の UI を実装する
  • リアルタイムデータをアニメーション付きの視覚化にバインドする
  • ユーザー入力に応答するアニメーションを作成する
  • ランタイム制御が必要な、デザイナー作成のアニメーションを扱う

代替手段:

  • Lottie(lottie-animations):ステートマシンを使わない、シンプルなタイムラインベースのアニメーション向け
  • Framer Motion(motion-framer):スプリング物理を使った、コードファーストの React アニメーション向け
  • GSAP(gsap-scrolltrigger):細かな制御が可能な、タイムラインベースの Web アニメーション向け

主な概念

1. ステートマシン

ステートマシンは、状態と遷移によってアニメーションの動作を定義します。

  • 状態:異なるアニメーション状態(例:アイドル、ホバー、押下)
  • 入力:遷移を制御する変数(ブール値、数値、トリガー)
  • 遷移:状態間を移動するためのルール
  • リスナー:状態変化に応答する React フック
2. 入力

3 種類の入力がステートマシンの動作を制御します。

  • ブール値:オン/オフの状態(例:isHovered、isActive)
  • 数値:数値(例:progress、volume)
  • トリガー:1 回限りのイベント(例:クリック、送信)
3. ViewModel

動的なプロパティのためのデータバインディングシステムです。

  • 文字列プロパティ:テキストコンテンツ(例:ユーザー名、タイトル)
  • 数値プロパティ:数値データ(例:株価、スコア)
  • 色プロパティ:動的な色(16 進数値)
  • 列挙型プロパティ:事前定義された選択肢からの選択
  • トリガープロパティ:アニメーションイベント
4. イベント

アニメーションから発行されるカスタムイベントです。

  • 一般イベント:カスタム名のイベント
  • イベントプロパティ:イベントに付加されるデータ
  • イベントリスナー:イベントを処理する React フック

よくあるパターン

パターン 1:基本的な Rive アニメーション

使用例:React でシンプルな Rive アニメーションを表示する

実装:

# Installation
npm install rive-react
import Rive from 'rive-react';

export default function SimpleAnimation() {
  return (
    <Rive
      src="animation.riv"
      artboard="Main"
      animations="idle"
      layout={{ fit: "contain", alignment: "center" }}
      style={{ width: '400px', height: '400px' }}
    />
  );
}

ポイント:

  • src:.riv ファイルへのパス
  • artboard:表示するアートボード
  • animations:再生するアニメーションタイムライン
  • layout:アニメーションをコンテナにどのように収めるか
パターン 2:入力によるステートマシンの制御

使用例:ユーザー操作に応じてアニメーションの状態を制御する

実装:

import { useRive, useStateMachineInput } from 'rive-react';

export default function InteractiveButton() {
  const { rive, RiveComponent } = useRive({
    src: 'button.riv',
    stateMachines: 'Button State Machine',
    autoplay: true,
  });

  // Get state machine inputs
  const hoverInput = useStateMachineInput(
    rive,
    'Button State Machine',
    'isHovered',
    false
  );

  const clickInput = useStateMachineInput(
    rive,
    'Button State Machine',
    'isClicked',
    false
  );

  return (
    <div
      onMouseEnter={() => hoverInput && (hoverInput.value = true)}
      onMouseLeave={() => hoverInput && (hoverInput.value = false)}
      onClick={() => clickInput && clickInput.fire()} // Trigger input
      style={{ cursor: 'pointer' }}
    >
      <RiveComponent style={{ width: '200px', height: '100px' }} />
    </div>
  );
}

入力の種類:

  • ブール値:input.value = true/false
  • 数値:input.value = 50
  • トリガー:input.fire()
パターン 3:ViewModel のデータバインディング

使用例:アプリケーションのデータをアニメーションのプロパティにバインドする

実装:

import { useRive, useViewModel, useViewModelInstance,
         useViewModelInstanceString, useViewModelInstanceNumber } from 'rive-react';
import { useEffect, useState } from 'react';

export default function Dashboard() {
  const [stockPrice, setStockPrice] = useState(150.0);

  const { rive, RiveComponent } = useRive({
    src: 'dashboard.riv',
    autoplay: true,
    autoBind: false, // Manual binding for ViewModels
  });

  // Get ViewModel and instance
  const viewModel = useViewModel(rive, { name: 'Dashboard' });
  const viewModelInstance = useViewModelInstance(viewModel, { rive });

  // Bind properties
  const { setValue: setTitle } = useViewModelInstanceString(
    'title',
    viewModelInstance
  );

  const { setValue: setPrice } = useViewModelInstanceNumber(
    'stockPrice',
    viewModelInstance
  );

  useEffect(() => {
    if (setTitle) setTitle('Stock Dashboard');
  }, [setTitle]);

  useEffect(() => {
    if (setPrice) setPrice(stockPrice);
  }, [setPrice, stockPrice]);

  // Simulate real-time updates
  useEffect(() => {
    const interval = setInterval(() => {
      setStockPrice((prev) => prev + (Math.random() - 0.5) * 10);
    }, 1000);

    return () => clearInterval(interval);
  }, []);

  return <RiveComponent style={{ width: '800px', height: '600px' }} />;
}

ViewModel プロパティフック:

  • useViewModelInstanceString - テキストプロパティ
  • useViewModelInstanceNumber - 数値プロパティ
  • useViewModelInstanceColor - 色プロパティ(16 進数)
  • useViewModelInstanceEnum - 列挙型の選択
  • useViewModelInstanceTrigger - アニメーショントリガー
パターン 4:Rive イベントの処理

使用例:Rive アニメーションから発行されるイベントに応答する

実装:

import { useRive, EventType, RiveEventType } from 'rive-react';
import { useEffect } from 'react';

export default function InteractiveRating() {
  const { rive, RiveComponent } = useRive({
    src: 'rating.riv',
    stateMachines: 'State Machine 1',
    autoplay: true,
    automaticallyHandleEvents: true,
  });

  useEffect(() => {
    if (!rive) return;

    const onRiveEvent = (event) => {
      const eventData = event.data;

      if (eventData.type === RiveEventType.General) {
        console.log('Event:', eventData.name);

        // Access event properties
        const rating = eventData.properties.rating;
        const message = eventData.properties.message;

        if (rating >= 4) {
          alert(`Thanks for ${rating} stars: ${message}`);
        }
      }
    };

    rive.on(EventType.RiveEvent, onRiveEvent);

    return () => {
      rive.off(EventType.RiveEvent, onRiveEvent);
    };
  }, [rive]);

  return <RiveComponent style={{ width: '400px', height: '300px' }} />;
}
パターン 5:Rive ファイルのプリロード

使用例:アニメーションをプリロードして読み込み時間を最適化する

実装:

import { useRiveFile, useRive } from 'rive-react';

export default function PreloadedAnimation() {
  const { riveFile, status } = useRiveFile({
    src: 'large-animation.riv',
  });

  const { RiveComponent } = useRive({
    riveFile: riveFile,
    artboard: 'Main',
    autoplay: true,
  });

  if (status === 'loading') {
    return <div>Loading animation...</div>;
  }

  if (status === 'failed') {
    return <div>Failed to load animation</div>;
  }

  return <RiveComponent style={{ width: '600px', height: '400px' }} />;
}
パターン 6:Ref によるアニメーションの制御

使用例:親コンポーネントからアニメーションを制御する

実装:

import { useRive, useViewModel, useViewModelInstance,
         useViewModelInstanceTrigger } from 'rive-react';
import { useImperativeHandle, forwardRef } from 'react';

const AnimatedComponent = forwardRef((props, ref) => {
  const { rive, RiveComponent } = useRive({
    src: 'logo.riv',
    autoplay: true,
    autoBind: false,
  });

  const viewModel = useViewModel(rive, { useDefault: true });
  const viewModelInstance = useViewModelInstance(viewModel, { rive });

  const { trigger: spinTrigger } = useViewModelInstanceTrigger(
    'triggerSpin',
    viewModelInstance
  );

  // Expose methods to parent
  useImperativeHandle(ref, () => ({
    spin: () => spinTrigger && spinTrigger(),
    pause: () => rive && rive.pause(),
    play: () => rive && rive.play(),
  }));

  return <RiveComponent style={{ width: '200px', height: '200px' }} />;
});

export default function App() {
  const animationRef = useRef();

  return (
    <div>
      <AnimatedComponent ref={animationRef} />
      <button onClick={() => animationRef.current?.spin()}>Spin</button>
      <button onClick={() => animationRef.current?.pause()}>Pause</button>
    </div>
  );
}
パターン 7:複数のプロパティを持つ ViewModel の更新

ユースケース:複雑なデータから複数のアニメーションプロパティを更新します

実装:

import { useRive, useViewModel, useViewModelInstance,
         useViewModelInstanceString, useViewModelInstanceNumber,
         useViewModelInstanceColor } from 'rive-react';
import { useEffect } from 'react';

export default function UserProfile({ user }) {
  const { rive, RiveComponent } = useRive({
    src: 'profile.riv',
    autoplay: true,
    autoBind: false,
  });

  const viewModel = useViewModel(rive, { useDefault: true });
  const viewModelInstance = useViewModelInstance(viewModel, { rive });

  // Bind all properties
  const { setValue: setName } = useViewModelInstanceString('name', viewModelInstance);
  const { setValue: setScore } = useViewModelInstanceNumber('score', viewModelInstance);
  const { setValue: setColor } = useViewModelInstanceColor('avatarColor', viewModelInstance);

  useEffect(() => {
    if (user && setName && setScore && setColor) {
      setName(user.name);
      setScore(user.score);
      setColor(parseInt(user.color.substring(1), 16)); // Convert hex to number
    }
  }, [user, setName, setScore, setColor]);

  return <RiveComponent style={{ width: '300px', height: '300px' }} />;
}

統合パターン

Framer Motion(motion-framer)との連携

Rive がインタラクティブなコンテンツを処理する間、コンテナーをアニメーション化します:

import { motion } from 'framer-motion';
import Rive from 'rive-react';

export default function AnimatedCard() {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      whileHover={{ scale: 1.05 }}
    >
      <Rive
        src="card.riv"
        stateMachines="Card State Machine"
        style={{ width: '300px', height: '400px' }}
      />
    </motion.div>
  );
}
GSAP ScrollTrigger(gsap-scrolltrigger)との連携

スクロール時に Rive アニメーションをトリガーします:

import { useRive, useStateMachineInput } from 'rive-react';
import { useEffect, useRef } from 'react';
import gsap from 'gsap';
import ScrollTrigger from 'gsap/ScrollTrigger';

gsap.registerPlugin(ScrollTrigger);

export default function ScrollRive() {
  const containerRef = useRef();
  const { rive, RiveComponent } = useRive({
    src: 'scroll-animation.riv',
    stateMachines: 'State Machine 1',
    autoplay: true,
  });

  const trigger = useStateMachineInput(rive, 'State Machine 1', 'trigger');

  useEffect(() => {
    if (!trigger) return;

    ScrollTrigger.create({
      trigger: containerRef.current,
      start: 'top center',
      onEnter: () => trigger.fire(),
    });
  }, [trigger]);

  return (
    <div ref={containerRef}>
      <RiveComponent style={{ width: '100%', height: '600px' }} />
    </div>
  );
}

パフォーマンスの最適化

1. オフスクリーンレンダラーを使用する
<Rive
  src="animation.riv"
  useOffscreenRenderer={true} // Better performance
/>
2. Rive ファイルを最適化する

Rive エディターで:

  • アートボードのサイズを 2MB 未満に保つ
  • ベクターグラフィックスを使用する(可能な場合はラスター画像を避ける)
  • スケルタルアニメーションのボーン数を最小限にする
  • ステートマシンの複雑さを軽減する
3. 重要なアニメーションを事前に読み込む
const { riveFile } = useRiveFile({ src: 'critical.riv' });
// Preload during app initialization
4. 自動イベント処理を無効にする
<Rive
  src="animation.riv"
  automaticallyHandleEvents={false} // Manual control
/>

よくある問題と解決策

問題 1:ステートマシンの入力が見つからない

問題:useStateMachineInput が null を返します

解決策:

// ❌ Wrong: Incorrect input name
const input = useStateMachineInput(rive, 'State Machine', 'wrongName');

// ✅ Correct: Match exact name from Rive editor
const input = useStateMachineInput(rive, 'State Machine', 'isHovered');

// Always check if input exists before using
if (input) {
  input.value = true;
}
問題 2:ViewModel のプロパティが更新されない

問題:ViewModel のプロパティを変更してもアニメーションが更新されません

解決策:

// ❌ Wrong: autoBind enabled
const { rive } = useRive({
  src: 'dashboard.riv',
  autoplay: true,
  // autoBind: true (default)
});

// ✅ Correct: Disable autoBind for ViewModels
const { rive } = useRive({
  src: 'dashboard.riv',
  autoplay: true,
  autoBind: false, // Required for manual ViewModel control
});
問題 3:イベントリスナーが呼び出されない

問題:Rive イベントによってコールバックがトリガーされません

解決策:

// ❌ Wrong: Missing automaticallyHandleEvents
const { rive } = useRive({
  src: 'rating.riv',
  stateMachines: 'State Machine 1',
  autoplay: true,
});

// ✅ Correct: Enable event handling
const { rive } = useRive({
  src: 'rating.riv',
  stateMachines: 'State Machine 1',
  autoplay: true,
  automaticallyHandleEvents: true, // Required for events
});

リソース

公式ドキュメント
Rive エディター
学習リソース

関連スキル

  • lottie-animations:ステートマシンを使わない、よりシンプルなタイムラインベースのアニメーション向け
  • motion-framer:ジェスチャーを使った、コード中心の React アニメーション向け
  • gsap-scrolltrigger:スクロール駆動型アニメーション向け
  • spline-interactive:3D インタラクティブアニメーション向け

スクリプト

このスキルにはユーティリティスクリプトが含まれています:

  • component_generator.py - Rive React コンポーネントのボイラープレートを生成します
  • viewmodel_builder.py - ViewModel のプロパティバインディングを構築します

スキルディレクトリからスクリプトを実行します:

./scripts/component_generator.py
./scripts/viewmodel_builder.py

アセット

スターターテンプレートと例:

  • starter_rive/ - React + Rive の完全なテンプレート
  • examples/ - 実用的な統合パターン

コメント