rive-interactive について
Rive Interactive は、デザイナーや開発者向けの、ステートマシン駆動のベクターアニメーションを扱うスキルです。入力、イベント、ViewModel のバインディングにより、コードから状態やプロパティを制御できます。
主な機能
- 01ステートマシンと状態遷移
- 02Boolean・数値・トリガー入力
- 03ViewModel の双方向データバインディング
- 04カスタムアニメーションイベントとリスナー
- 05実行時のプロパティ制御
活用シーン
- 01複数の状態を持つ UI アニメーションの作成
- 02ホバーやクリックなどの入力への応答
- 03リアルタイムデータをアニメーション表示に連携
- 04React コンポーネントでのアニメーションイベント処理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ freshtechbro/ claudedesignskills --skill rive-interactiveインストール後、Claude Code で/rive-interactiveを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/README.mdreferences/api_reference.mdscripts/component_generator.pyviewmodel_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 ドキュメント:https://rive.app/docs
- React Rive GitHub:https://github.com/rive-app/rive-react
- Rive コミュニティ:https://rive.app/community
Rive エディター
- ウェブエディター:https://rive.app/community
- デスクトップアプリ:macOS、Windows で利用できます
学習リソース
- チュートリアル:https://rive.app/learn
- 例:https://rive.app/community/files
- ステートマシンガイド:https://rive.app/docs/state-machine
関連スキル
- 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/- 実用的な統合パターン