rive-interactive by freshtechbro/claudedesignskills
npx skills add https://github.com/freshtechbro/claudedesignskills --skill rive-interactiveRive 是一个基于状态机的动画平台,使设计师能够创建具有复杂逻辑和运行时交互性的交互式矢量动画。与仅支持时间线的动画工具(如 Lottie)不同,Rive 支持状态机、输入处理以及应用程序代码与动画之间的双向数据绑定。
主要特性:
何时使用此技能:
替代方案:
状态机通过状态和转换定义动画行为:
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
三种输入类型控制状态机行为:
用于动态属性的数据绑定系统:
从动画发出的自定义事件:
使用场景:在 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:动画在容器中的适配方式使用场景:基于用户交互控制动画状态
实现:
import { useRive, useStateMachineInput } from 'rive-react';
export default function InteractiveButton() {
const { rive, RiveComponent } = useRive({
src: 'button.riv',
stateMachines: 'Button State Machine',
autoplay: true,
});
// 获取状态机输入
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()} // 触发器输入
style={{ cursor: 'pointer' }}
>
<RiveComponent style={{ width: '200px', height: '100px' }} />
</div>
);
}
输入类型:
input.value = true/falseinput.value = 50input.fire()使用场景:将应用程序数据绑定到动画属性
实现:
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, // ViewModels 的手动绑定
});
// 获取 ViewModel 和实例
const viewModel = useViewModel(rive, { name: 'Dashboard' });
const viewModelInstance = useViewModelInstance(viewModel, { rive });
// 绑定属性
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]);
// 模拟实时更新
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 - 颜色属性(十六进制)useViewModelInstanceEnum - 枚举选择useViewModelInstanceTrigger - 动画触发器使用场景:响应从 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);
// 访问事件属性
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' }} />;
}
使用场景:通过预加载动画优化加载时间
实现:
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' }} />;
}
使用场景:从父组件控制动画
实现:
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
);
// 向父组件暴露方法
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>
);
}
使用场景:从复杂数据更新多个动画属性
实现:
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 });
// 绑定所有属性
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)); // 将十六进制转换为数字
}
}, [user, setName, setScore, setColor]);
return <RiveComponent style={{ width: '300px', height: '300px' }} />;
}
在 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>
);
}
在滚动时触发 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>
);
}
<Rive
src="animation.riv"
useOffscreenRenderer={true} // 更好的性能
/>
在 Rive 编辑器中:
const { riveFile } = useRiveFile({ src: 'critical.riv' });
// 在应用初始化期间预加载
<Rive
src="animation.riv"
automaticallyHandleEvents={false} // 手动控制
/>
问题:useStateMachineInput 返回 null
解决方案:
// ❌ 错误:输入名称不正确
const input = useStateMachineInput(rive, 'State Machine', 'wrongName');
// ✅ 正确:匹配 Rive 编辑器中的确切名称
const input = useStateMachineInput(rive, 'State Machine', 'isHovered');
// 在使用前始终检查输入是否存在
if (input) {
input.value = true;
}
问题:ViewModel 属性未更新动画
解决方案:
// ❌ 错误:启用了 autoBind
const { rive } = useRive({
src: 'dashboard.riv',
autoplay: true,
// autoBind: true (默认)
});
// ✅ 正确:为 ViewModels 禁用 autoBind
const { rive } = useRive({
src: 'dashboard.riv',
autoplay: true,
autoBind: false, // 手动 ViewModel 控制所必需
});
问题:Rive 事件未触发回调
解决方案:
// ❌ 错误:缺少 automaticallyHandleEvents
const { rive } = useRive({
src: 'rating.riv',
stateMachines: 'State Machine 1',
autoplay: true,
});
// ✅ 正确:启用事件处理
const { rive } = useRive({
src: 'rating.riv',
stateMachines: 'State Machine 1',
autoplay: true,
automaticallyHandleEvents: true, // 事件处理所必需
});
此技能包含实用脚本:
component_generator.py - 生成 Rive React 组件样板代码viewmodel_builder.py - 构建 ViewModel 属性绑定从技能目录运行脚本:
./scripts/component_generator.py
./scripts/viewmodel_builder.py
入门模板和示例:
starter_rive/ - 完整的 React + Rive 模板examples/ - 真实世界的集成模式每周安装次数
82
仓库
GitHub 星标数
11
首次出现
2026年2月27日
安全审计
安装于
opencode82
github-copilot80
codex80
amp80
cline80
kimi-cli80
Rive is a state machine-based animation platform that enables designers to create interactive vector animations with complex logic and runtime interactivity. Unlike timeline-only animation tools (like Lottie), Rive supports state machines, input handling, and two-way data binding between application code and animations.
Key Features :
When to Use This Skill :
Alternatives :
State machines define animation behavior with states and transitions:
Three input types control state machine behavior:
Data binding system for dynamic properties:
Custom events emitted from animations:
Use Case : Display a simple Rive animation in React
Implementation :
# 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' }}
/>
);
}
Key Points :
src: Path to .riv fileartboard: Which artboard to displayanimations: Which animation timeline to playlayout: How animation fits in containerUse Case : Control animation states based on user interaction
Implementation :
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 Types :
input.value = true/falseinput.value = 50input.fire()Use Case : Bind application data to animation properties
Implementation :
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 Property Hooks :
useViewModelInstanceString - Text propertiesuseViewModelInstanceNumber - Numeric propertiesuseViewModelInstanceColor - Color properties (hex)useViewModelInstanceEnum - Enum selectionuseViewModelInstanceTrigger - Animation triggersUse Case : React to events emitted from Rive animation
Implementation :
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' }} />;
}
Use Case : Optimize load times by preloading animations
Implementation :
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' }} />;
}
Use Case : Control animation from parent component
Implementation :
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>
);
}
Use Case : Update multiple animation properties from complex data
Implementation :
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' }} />;
}
Animate container while Rive handles interactive content:
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>
);
}
Trigger Rive animations on scroll:
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>
);
}
<Rive
src="animation.riv"
useOffscreenRenderer={true} // Better performance
/>
In Rive Editor :
const { riveFile } = useRiveFile({ src: 'critical.riv' });
// Preload during app initialization
<Rive
src="animation.riv"
automaticallyHandleEvents={false} // Manual control
/>
Problem : useStateMachineInput returns null
Solution :
// ❌ 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;
}
Problem : ViewModel property doesn't update animation
Solution :
// ❌ 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
});
Problem : Rive events not triggering callback
Solution :
// ❌ 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
});
This skill includes utility scripts:
component_generator.py - Generate Rive React component boilerplateviewmodel_builder.py - Build ViewModel property bindingsRun scripts from the skill directory:
./scripts/component_generator.py
./scripts/viewmodel_builder.py
Starter templates and examples:
starter_rive/ - Complete React + Rive templateexamples/ - Real-world integration patternsWeekly Installs
82
Repository
GitHub Stars
11
First Seen
Feb 27, 2026
Security Audits
Gen Agent Trust HubPassSocketWarnSnykPass
Installed on
opencode82
github-copilot80
codex80
amp80
cline80
kimi-cli80
HeroUI v2 到 v3 迁移指南:破坏性变更、复合组件、Tailwind v4 升级
556 周安装
Rust Trait 探索器 - 快速查找 trait 实现与多态设计分析工具
232 周安装
阿里云轻量应用服务器SWAS-OPEN API管理指南:实例、磁盘、快照、镜像、防火墙
265 周安装
阿里云AIRec智能推荐管理技能 - 使用OpenAPI/SDK管理AI推荐引擎
265 周安装
阿里云文档API质量评审工具 - 自动化产品文档与OpenAPI评审报告生成
266 周安装
阿里云文档智能DocMind Node.js SDK使用教程:异步提取文档结构、文本和布局
266 周安装
Super Save - Claude 对话知识保存工具,高效管理项目记忆与团队协作
273 周安装