mcpField
R
rive-interactive

rive-interactive

github.com/freshtechbro设计与 UI1.2k2.6k 次安装49 周前更新
代码仓库

关于 rive-interactive

Rive Interactive 是面向设计师与开发者的状态机驱动矢量动画技能,支持应用代码与动画之间的交互。它通过输入、事件和 ViewModel 数据绑定控制动画状态与属性。

主要功能

  1. 01状态机与状态转换
  2. 02布尔值、数值和触发器输入
  3. 03ViewModel 双向数据绑定
  4. 04动画自定义事件与监听
  5. 05运行时属性控制

适用场景

  1. 01制作具有多种状态的界面动画
  2. 02响应悬停、点击等用户输入
  3. 03将实时数据绑定到动画视觉效果
  4. 04在 React 组件中处理动画事件

安装

复制下面的命令,在终端中运行。

$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 交互式状态机矢量动画

概述

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 编辑器
学习资源

相关技能

  • 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