精通 Rive 动画:React 开发者完整指南
Source: Dev.to
介绍
在现代网页开发中,打造生动且令人兴奋的用户体验(UX)不仅仅依赖于简单的 CSS 过渡。我们需要复杂、交互式的动画,这些动画 外观出色且不会拖慢应用。这正是 Rive 成为我们技术栈中强大“秘密武器”的原因。
今天,让我们一起探讨在项目中使用 Rive 的完整流程,从了解它的概念到设计架构,再到使用真实源码进行实现。
Part 1: Rive Basics – From Design to Code
1. 什么是 Rive?我们该如何创建它?
Rive 是一个完整的生态系统,包含 Design Tool(编辑器) 和用于代码的 Runtime 引擎。它不同于只播放 JSON 文件的 Lottie 或视频——Rive 更像是一台真正的交互式机器。
工作流程
使用 Rive 的感觉就像把 Figma(用于绘图)和 After Effects(用于运动)结合起来,再加上一点 Unity 游戏式的逻辑。
| 阶段 | 发生了什么 | 小贴士 |
|---|---|---|
| Design | 设计师在 Rive Editor(网页版或桌面版)中工作。他们可以直接在 Rive 中绘制,也可以从 Figma 导入素材。 | 始终为图层起清晰的名称(例如 UserAvatar、ScoreText),方便开发者在代码中查找。 |
| Animate | 设计师使用时间轴创建动作(例如 Run、Jump、Idle)。Rive 使用“骨骼”(skeletal animation)实现平滑的角色运动。 | – |
| State Machine (Logic) | 设计师将动画与逻辑连接。示例:当输入 isRunning 为 true 时,从 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 解决了难点:加载状态、错误处理,以及——最重要的——动态内容更新(在动画内部更改文字或图片)。

工作原理
Hook 接受包含 src、artboard 和 stateMachines 的配置对象。
// 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文件。 - 将常量、工具函数以及自定义的
useRiveAnimationHook 集中管理,以避免重复。 - 使用
updateMultipleContents动态更改文本、图像和状态机输入,使 UI 保持响应且代码库易于维护。
有了此设置,设计师可以打造丰富的交互体验,开发者则能在整个应用中干净高效地集成这些动画。
Source: …
rt 3:如何实现(分步指南)
让我们想象正在构建一个 倒计时小部件(类似于 WidgetManager/Templates/RiveAnimationTemplate.jsx)。
步骤 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”)。可以通过以下方式修复:
- 初始时将
autoplay: false设置。 - 等待
isLoaded。 - 调用
updateMultipleContents设置数据。 - 然后 调用
play()。
处理错误
始终提供回退方案。如果 .riv 文件加载失败(404),hook 中的 isError 变量会变为 true。利用它显示静态图片或关闭按钮,以防用户卡住。
结论
Rive 不仅仅是一个装饰工具。它将 运动逻辑 与 React 代码 分离,使设计师能够创建精美的动画,而开发者则专注于数据处理。
通过掌握 useRiveAnimation Hook,你可以构建外观高级、流畅无比的小部件、横幅和弹出层。祝编码愉快!

