关于 rive-interactive
Rive Interactive 是面向设计师与开发者的状态机驱动矢量动画技能,支持应用代码与动画之间的交互。它通过输入、事件和 ViewModel 数据绑定控制动画状态与属性。
主要功能
- 01状态机与状态转换
- 02布尔值、数值和触发器输入
- 03ViewModel 双向数据绑定
- 04动画自定义事件与监听
- 05运行时属性控制
适用场景
- 01制作具有多种状态的界面动画
- 02响应悬停、点击等用户输入
- 03将实时数据绑定到动画视觉效果
- 04在 React 组件中处理动画事件
安装
复制下面的命令,在终端中运行。
$
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 交互式状态机矢量动画
概述
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 Hook
2. 输入
三种输入类型可控制状态机行为:
- 布尔值:开/关状态(例如,isHovered、isActive)
- 数字:数值(例如,进度、音量)
- 触发器:一次性事件(例如,点击、提交)
3. ViewModel
用于动态属性的数据绑定系统:
- 字符串属性:文本内容(例如,用户名、标题)
- 数字属性:数值数据(例如,股价、分数)
- 颜色属性:动态颜色(十六进制值)
- 枚举属性:从预定义选项中选择
- 触发器属性:动画事件
4. 事件
从动画发出的自定义事件:
- 常规事件:自定义命名事件
- 事件属性:附加到事件的数据
- 事件监听器:用于处理事件的 React Hook
常见模式
模式 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 属性 Hook:
useViewModelInstanceString- 文本属性useViewModelInstanceNumber- 数字属性useViewModelInstanceColor- 颜色属性(十六进制)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:通过 Refs 控制动画
使用场景:从父组件控制动画
实现:
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/- 实际集成模式
评论
支持 Markdown