精通 Rive 动画:React 开发者完整指南

发布: (2025年12月17日 GMT+8 12:32)
9 min read
原文: Dev.to

Source: Dev.to

介绍

在现代网页开发中,打造生动且令人兴奋的用户体验(UX)不仅仅依赖于简单的 CSS 过渡。我们需要复杂、交互式的动画,这些动画 外观出色且不会拖慢应用。这正是 Rive 成为我们技术栈中强大“秘密武器”的原因。

今天,让我们一起探讨在项目中使用 Rive 的完整流程,从了解它的概念到设计架构,再到使用真实源码进行实现。

Part 1: Rive Basics – From Design to Code

1. 什么是 Rive?我们该如何创建它?

Rive 是一个完整的生态系统,包含 Design Tool(编辑器) 和用于代码的 Runtime 引擎。它不同于只播放 JSON 文件的 Lottie 或视频——Rive 更像是一台真正的交互式机器。

RiveFlow

工作流程

使用 Rive 的感觉就像把 Figma(用于绘图)和 After Effects(用于运动)结合起来,再加上一点 Unity 游戏式的逻辑。

阶段发生了什么小贴士
Design设计师在 Rive Editor(网页版或桌面版)中工作。他们可以直接在 Rive 中绘制,也可以从 Figma 导入素材。始终为图层起清晰的名称(例如 UserAvatarScoreText),方便开发者在代码中查找。
Animate设计师使用时间轴创建动作(例如 RunJumpIdle)。Rive 使用“骨骼”(skeletal animation)实现平滑的角色运动。
State Machine (Logic)设计师将动画与逻辑连接。示例:当输入 isRunningtrue 时,从 Idle 切换到 Running统一约定输入名称(Triggers、Numbers、Booleans),这样 React 能完美控制动画。

2. .REV.RIV 的区别

扩展名描述用途
.REV源项目文件(未压缩)。相当于 Photoshop 的 .PSD。请妥善保存以便后续编辑。不要在应用中发布此文件。设计与动画迭代。
.RIV运行时文件(二进制、已优化、体积小)。可直接在网页上运行。将其放入 src/assets 中使用。生产环境打包。

3. 哪里可以找到免费 Rive 文件?

如果想练手但没有设计师,可前往 Rive Community——它是“动画的 GitHub”。搜索 “Loading” 或 “Button”,点击 Remix 查看构建方式,然后免费导出 .riv 文件。

第 2 部分:我们架构中的技术实现

1. 项目结构

基于当前代码库,下面是我们的组织方式:

src/
├─ constants/
│   └─ rive.js               # 画板名称、状态机、输入名称(防止拼写错误)
├─ utils/
│   └─ riveUtils.js          # 快速创建配置的辅助函数
├─ hooks/
│   └─ useRiveAnimation.js   # 系统的“核心”——处理加载、更新、事件
└─ view/
    └─ shared/.../Templates  # 使用该 Hook 的 UI 组件(弹窗、横幅、部件)

2. 深入解析:useRiveAnimation Hook

该 Hook 解决了难点:加载状态、错误处理,以及——最重要的——动态内容更新(在动画内部更改文字或图片)。

integratewithhook

工作原理

Hook 接受包含 srcartboardstateMachines 的配置对象。

// src/hooks/useRiveAnimation.js (simplified)
const { rive, RiveComponent } = useRive({
  src: finalSrc,
  artboard,
  stateMachines,
  autoplay,
  // …other options
});

关键特性:动态更新

我们可以使用 updateMultipleContents 在运行中的动画文件里更改 文本(例如倒计时)或 图片(例如用户头像)。Hook 支持多种 RiveFieldType

RiveFieldType功能说明
String更改文本内容
Image替换动画内部的图片
Trigger / Boolean / Number控制动画的逻辑/流程

实现通过 Promise.allSettled 批量更新,以获得最佳性能:

// Efficient batch updating
const updateMultipleContents = useCallback(
  async (updates, artboardPath) => {
    // …logic to locate the view model (vmi)…
    const promises = updates.map(({ path, type, value }) => {
      switch (type) {
        case RiveFieldType.String:
          return updateStringValue(vmi, path, value);
        case RiveFieldType.Image:
          return updateImageValue(vmi, path, value);
        // …handle other types…
      }
    });
    await Promise.allSettled(promises);
  },
  [rive]
);

3. 在组件中使用 Hook

import { useRiveAnimation } from '@/hooks/useRiveAnimation';
import { RIVE_ASSETS } from '@/constants/rive';

export const AvatarBadge = ({ avatarUrl, userName }) => {
  const { RiveComponent, setInput, updateMultipleContents } = useRiveAnimation({
    src: RIVE_ASSETS.avatarBadge,
    artboard: 'Main',
    stateMachines: ['BadgeSM'],
    autoplay: true,
  });

  // 当 props 变化时更新头像图片和名称
  useEffect(() => {
    updateMultipleContents(
      [
        { path: 'Avatar/Image', type: RiveFieldType.Image, value: avatarUrl },
        { path: 'Name/Text', type: RiveFieldType.String, value: userName },
      ],
      'Main'
    );
  }, [avatarUrl, userName]);

  // 示例:点击时触发 “pulse” 动画
  const handleClick = () => setInput('PulseTrigger', true);

  return (
    <div onClick={handleClick}>
      <RiveComponent />
    </div>
  );
};

Recap

  • Rive 为我们提供了设计优先、代码就绪的动画工作流。
  • 保留 .rev 文件用于迭代;仅发布 .riv 文件。
  • 将常量、工具函数以及自定义的 useRiveAnimation Hook 集中管理,以避免重复。
  • 使用 updateMultipleContents 动态更改文本、图像和状态机输入,使 UI 保持响应且代码库易于维护。

有了此设置,设计师可以打造丰富的交互体验,开发者则能在整个应用中干净高效地集成这些动画。

Source:

rt 3:如何实现(分步指南)

让我们想象正在构建一个 倒计时小部件(类似于 WidgetManager/Templates/RiveAnimationTemplate.jsx)。

Guide

步骤 1:准备常量

定义设计师在 Rive 文件中创建的输入。

// src/constants/rive.js
export const WIDGET_STATE_MACHINE = "Widget SM";

export const RiveInputs = {
  WidgetIn: "Widget In",   // 触发器:播放出现动画
  WidgetHover: "Hover",     // 布尔值:鼠标是否悬停?
};

export const RiveFields = {
  Time: "Timer/Time",      // Rive 中文本对象的路径
};

步骤 2:在组件中使用 Hook

连接 Hook。始终在显示组件前检查 isLoaded

const WidgetRive = ({ endTime }) => {
  const {
    RiveComponent,
    isLoaded,
    updateMultipleContents,
    triggerStateMachineInput,
  } = useRiveAnimation({
    src: "assets/widget.riv",
    artboard: "Main",
    stateMachines: WIDGET_STATE_MACHINE,
    autoplay: false, // 我们稍后手动播放
  });

  // 每秒更新一次计时器
  useEffect(() => {
    if (isLoaded && endTime) {
      const timeStr = calculateTimeLeft(endTime);
      // 将新文本发送给 Rive
      updateMultipleContents([
        new RivePropertyUpdate(
          RiveFields.Time,
          RiveFieldType.String,
          timeStr
        ),
      ]);
    }
  }, [isLoaded, endTime]);

  return (
    <>
      {!isLoaded && <Spinner />}
      {isLoaded && <RiveComponent />}
    </>
  );
};

步骤 3:处理交互

在我们的 BannerManager 中,轻松处理用户点击和悬停:

const handleMouseEnter = () => {
  // 告诉 Rive 鼠标已悬停在横幅上
  triggerStateMachineInput(
    WIDGET_STATE_MACHINE,
    RiveInputs.WidgetHover,
    true
  );
};

const handleMouseLeave = () => {
  triggerStateMachineInput(
    WIDGET_STATE_MACHINE,
    RiveInputs.WidgetHover,
    false
  );
};

第4部分:最佳实践与技巧

为了保持动画流畅(60 FPS)且无错误,请遵循我们源代码中的这些技巧。

缓存视图模型

我们的 hook 会缓存实例(vmiDataRef)。这至关重要——如果每秒更新一次计时器而不进行缓存,Rive 必须每分钟搜索文本对象 60 次,这会导致卡顿。

资源加载守护

在文件实际下载完成之前不要渲染 Rive 组件,尤其是在弹出窗口中。

if (isError) {
  return <FallbackImage />;
}
return isLoaded ? <RiveComponent /> : <LoadingSpinner />;

防止“闪烁”

在数据加载之前,Rive 可能会短暂显示默认文本(“Text”)。可以通过以下方式修复:

  1. 初始时将 autoplay: false 设置。
  2. 等待 isLoaded
  3. 调用 updateMultipleContents 设置数据。
  4. 然后 调用 play()

处理错误

始终提供回退方案。如果 .riv 文件加载失败(404),hook 中的 isError 变量会变为 true。利用它显示静态图片或关闭按钮,以防用户卡住。

结论

Rive 不仅仅是一个装饰工具。它将 运动逻辑React 代码 分离,使设计师能够创建精美的动画,而开发者则专注于数据处理。

通过掌握 useRiveAnimation Hook,你可以构建外观高级、流畅无比的小部件、横幅和弹出层。祝编码愉快!

Back to Blog

相关文章

阅读更多 »