重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
npx skills add https://github.com/bowtiedswan/rive-skills --skill rive此技能提供关于 Rive 的全面知识,Rive 是一个交互式动画平台,支持在 Web、移动端和游戏平台上创建和运行动态图形。
Rive 是一款设计和动画工具,能够生成轻量级、交互式的图形,并拥有强大的运行时环境。主要功能包括:
在以下情况下使用此技能:
import { useRive } from '@rive-app/react-canvas';
function MyAnimation() {
const { rive, RiveComponent } = useRive({
src: '/animation.riv',
stateMachines: 'MainStateMachine',
autoplay: true,
});
return <RiveComponent style={{ width: 400, height: 400 }} />;
}
const { rive, RiveComponent } = useRive({
src: '/animation.riv',
stateMachines: 'State Machine 1',
});
// 获取并设置输入
const scrollInput = rive?.stateMachineInputs('State Machine 1')
?.find(i => i.name === 'scrollProgress');
// 在滚动时更新(0-100 范围示例)
scrollInput?.value = scrollProgress * 100;
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
Rive 脚本使用 Luau(一种 Lua 变体)并遵循特定的协议。详细的 API 参考,请参阅 @references/rive-scripting-api.md。
| 协议 | 用途 | 关键函数 |
|---|---|---|
| Node | 自定义绘图/渲染 | init()、advance(seconds)、draw(renderer) |
| Layout | 自定义布局行为 | measure()、resize(size) + Node 函数 |
| Converter | 数据转换 | convert(input)、reverseConvert(input) |
| PathEffect | 路径修改 | init()、update(pathData)、advance(seconds) |
| Test | 测试框架 | 单元测试脚本 |
-- Define script data type with inputs
type MyNode = {
color: Input<Color>,
speed: Input<number>,
path: Path,
paint: Paint,
}
function init(self: MyNode): boolean
self.path = Path.new()
self.paint = Paint.new()
self.paint.style = 'fill'
return true
end
function advance(self: MyNode, seconds: number): boolean
-- Animation logic here, called every frame
return true -- Return true to keep receiving advance calls
end
function update(self: MyNode)
-- Called when any input changes
end
function draw(self: MyNode, renderer: Renderer)
renderer:drawPath(self.path, self.paint)
end
return function(): Node<MyNode>
return {
init = init,
advance = advance,
update = update,
draw = draw,
color = Color.rgba(255, 255, 255, 255),
speed = 1.0,
path = late(),
paint = late(),
}
end
type MyLayout = {
spacing: Input<number>,
}
function measure(self: MyLayout): Vec2D
-- Return desired size (used when Fit type is Hug)
return Vec2D.xy(200, 100)
end
function resize(self: MyLayout, size: Vec2D)
-- Called when layout receives new size
print("New size:", size.x, size.y)
end
-- Include Node functions (init, advance, draw) as needed
return function(): Layout<MyLayout>
return {
measure = measure,
resize = resize,
spacing = 10,
}
end
type NumberToString = {}
function convert(self: NumberToString, input: DataInputs): DataOutput
local dv: DataValueString = DataValue.string()
if input:isNumber() then
dv.value = tostring((input :: DataValueNumber).value)
else
dv.value = ""
end
return dv
end
function reverseConvert(self: NumberToString, input: DataOutput): DataInputs
local dv: DataValueNumber = DataValue.number()
if input:isString() then
dv.value = tonumber((input :: DataValueString).value) or 0
end
return dv
end
return function(): Converter<NumberToString, DataValueNumber, DataValueString>
return {
convert = convert,
reverseConvert = reverseConvert,
}
end
type WaveEffect = {
amplitude: Input<number>,
frequency: Input<number>,
context: Context,
}
function init(self: WaveEffect, context: Context): boolean
self.context = context
return true
end
function update(self: WaveEffect, inPath: PathData): PathData
local path = Path.new()
-- Transform path geometry here
for i = 1, #inPath do
local cmd = inPath[i]
-- Process each PathCommand
end
return path
end
function advance(self: WaveEffect, seconds: number): boolean
-- Called each frame for animated effects
return true
end
return function(): PathEffect<WaveEffect>
return {
init = init,
update = update,
advance = advance,
amplitude = 10,
frequency = 1,
context = late(),
}
end
定义输入以在 Rive 编辑器中暴露可配置属性:
type MyNode = {
-- Basic inputs
myNumber: Input<number>,
myColor: Input<Color>,
myString: string, -- Non-input, internal use only
-- View Model inputs
myViewModel: Input<Data.Character>,
-- Artboard inputs (for dynamic instantiation)
enemyTemplate: Input<Artboard<Data.Enemy>>,
}
-- Access input values
function init(self: MyNode): boolean
print("Number:", self.myNumber)
print("Color:", self.myColor)
print("ViewModel property:", self.myViewModel.health.value)
return true
end
-- Listen for changes
function init(self: MyNode): boolean
self.myNumber:addListener(function()
print("myNumber changed!")
end)
return true
end
-- Mark inputs assigned at runtime
return function(): Node<MyNode>
return {
init = init,
myNumber = 0,
myColor = Color.rgba(255, 255, 255, 255),
myString = "default",
myViewModel = late(), -- Assigned via Editor
enemyTemplate = late(),
}
end
处理触摸/鼠标交互:
function pointerDown(self: MyNode, event: PointerEvent)
print("Position:", event.position.x, event.position.y)
print("Pointer ID:", event.id) -- For multi-touch
event:hit() -- Mark as handled
end
function pointerMove(self: MyNode, event: PointerEvent)
-- Handle drag
event:hit()
end
function pointerUp(self: MyNode, event: PointerEvent)
event:hit()
end
function pointerExit(self: MyNode, event: PointerEvent)
event:hit()
end
return function(): Node<MyNode>
return {
pointerDown = pointerDown,
pointerMove = pointerMove,
pointerUp = pointerUp,
pointerExit = pointerExit,
}
end
在运行时创建画板实例:
type Enemy = {
artboard: Artboard<Data.Enemy>,
position: Vec2D,
}
type GameScene = {
enemyTemplate: Input<Artboard<Data.Enemy>>,
enemies: { Enemy },
}
function createEnemy(self: GameScene, x: number, y: number)
local enemy = self.enemyTemplate:instance()
local entry: Enemy = {
artboard = enemy,
position = Vec2D.xy(x, y),
}
table.insert(self.enemies, entry)
end
function advance(self: GameScene, seconds: number): boolean
for _, enemy in self.enemies do
enemy.artboard:advance(seconds)
end
return true
end
function draw(self: GameScene, renderer: Renderer)
for _, enemy in self.enemies do
renderer:save()
renderer:transform(Mat2D.fromTranslate(enemy.position.x, enemy.position.y))
enemy.artboard:draw(renderer)
renderer:restore()
end
end
从脚本访问视图模型:
function init(self: MyNode, context: Context): boolean
local vm = context:viewModel()
-- Get properties
local score = vm:getNumber('score')
local name = vm:getString('playerName')
-- Set values
if score then
score.value = 100
end
-- Listen for changes
if score then
score:addListener(function()
print("Score changed to:", score.value)
end)
end
-- Access nested view models
local settings = vm:getViewModel('settings')
local volume = settings:getNumber('volume')
return true
end
完整的 API 参考,请参阅 @references/rive-scripting-api.md。
local path = Path.new()
-- Drawing commands
path:moveTo(Vec2D.xy(0, 0))
path:lineTo(Vec2D.xy(100, 0))
path:quadTo(Vec2D.xy(150, 50), Vec2D.xy(100, 100))
path:cubicTo(Vec2D.xy(75, 150), Vec2D.xy(25, 150), Vec2D.xy(0, 100))
path:close()
-- Reset path for reuse
path:reset()
-- Measure path
local length = path:measure()
local contours = path:contours()
local paint = Paint.new()
paint.style = 'fill' -- or 'stroke'
paint.color = Color.rgba(255, 128, 0, 255)
paint.thickness = 3 -- For strokes
paint.cap = 'round' -- 'butt', 'round', 'square'
paint.join = 'round' -- 'miter', 'round', 'bevel'
paint.blendMode = 'srcOver'
-- Gradient fills
paint.gradient = Gradient.linear(
Vec2D.xy(0, 0),
Vec2D.xy(100, 100),
{ GradientStop.new(0, Color.hex('#FF0000')),
GradientStop.new(1, Color.hex('#0000FF')) }
)
function draw(self: MyNode, renderer: Renderer)
renderer:save()
-- Transform
renderer:transform(Mat2D.fromScale(2, 2))
renderer:transform(Mat2D.fromRotation(math.pi / 4))
renderer:transform(Mat2D.fromTranslate(50, 50))
-- Draw path
renderer:drawPath(self.path, self.paint)
-- Draw image
renderer:drawImage(self.image, ImageSampler.linear, 'srcOver', 1.0)
-- Clipping
renderer:clipPath(self.clipPath)
renderer:restore()
end
详细的运行时 API,请参阅 @references/rive-react-runtime.md。
# Recommended
npm install @rive-app/react-canvas
# Alternative options
npm install @rive-app/react-canvas-lite # Smaller, no Rive Text
npm install @rive-app/react-webgl # WebGL renderer
npm install @rive-app/react-webgl2 # Rive Renderer (WebGL2)
import { useRive, useStateMachineInput } from '@rive-app/react-canvas';
function Animation() {
const { rive, RiveComponent } = useRive({
src: '/animation.riv',
artboard: 'MainArtboard',
stateMachines: 'StateMachine1',
autoplay: true,
layout: new Layout({
fit: Fit.Contain,
alignment: Alignment.Center,
}),
});
// Playback control
const play = () => rive?.play();
const pause = () => rive?.pause();
const stop = () => rive?.stop();
return (
<RiveComponent
style={{ width: '100%', height: '100vh' }}
onMouseEnter={() => rive?.play()}
/>
);
}
function InteractiveAnimation() {
const { rive, RiveComponent } = useRive({
src: '/interactive.riv',
stateMachines: 'Controls',
autoplay: true,
});
useEffect(() => {
if (!rive) return;
const inputs = rive.stateMachineInputs('Controls');
// Number input
const progress = inputs?.find(i => i.name === 'progress');
if (progress) progress.value = 50;
// Boolean input
const isActive = inputs?.find(i => i.name === 'isActive');
if (isActive) isActive.value = true;
// Trigger input
const onClick = inputs?.find(i => i.name === 'onClick');
onClick?.fire();
}, [rive]);
return <RiveComponent />;
}
function ScrollAnimation() {
const { rive, RiveComponent } = useRive({
src: '/scroll-animation.riv',
stateMachines: 'ScrollMachine',
autoplay: true,
});
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!rive) return;
const progressInput = rive.stateMachineInputs('ScrollMachine')
?.find(i => i.name === 'scrollProgress');
const handleScroll = () => {
if (!containerRef.current || !progressInput) return;
const rect = containerRef.current.getBoundingClientRect();
const windowHeight = window.innerHeight;
// Calculate scroll progress (0-100)
const progress = Math.max(0, Math.min(100,
((windowHeight - rect.top) / (windowHeight + rect.height)) * 100
));
progressInput.value = progress;
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [rive]);
return (
<div ref={containerRef} style={{ height: '200vh' }}>
<div style={{ position: 'sticky', top: 0, height: '100vh' }}>
<RiveComponent style={{ width: '100%', height: '100%' }} />
</div>
</div>
);
}
function AnimationWithEvents() {
const { rive, RiveComponent } = useRive({
src: '/events.riv',
stateMachines: 'Main',
autoplay: true,
});
useEffect(() => {
if (!rive) return;
// Listen for Rive events
rive.on('statechange', (event) => {
console.log('State changed:', event.data);
});
rive.on('riveevent', (event) => {
console.log('Rive event:', event.data.name);
});
}, [rive]);
return <RiveComponent />;
}
local anim = artboard:animation('AnimationName')
-- Control
anim:advance(0.016) -- Advance by time in seconds
anim:setTime(1.5) -- Set time in seconds
anim:setTimeFrames(30) -- Set time in frames
anim:setTimePercentage(0.5) -- Set time as 0-1
-- Properties
local duration = anim.duration
local artboard = self.myArtboard:instance()
-- Properties
artboard.width = 400
artboard.height = 300
artboard.frameOrigin = true
-- Control
artboard:advance(seconds)
artboard:draw(renderer)
-- Access nodes and bounds
local node = artboard:node('NodeName')
local minPt, maxPt = artboard:bounds()
-- Pointer events
artboard:pointerDown(event)
artboard:pointerUp(event)
artboard:pointerMove(event)
init() 中创建,在 draw() 中重用scrollProgress 输入:将滚动位置映射到 0-100 范围sticky 定位:用于滚动触发的场景autoplay 和状态机名称-- In scripts
print("Debug:", value)
-- Check Problems Panel in Rive Editor
-- Use Debug Panel for runtime inspection
详细的 API 参考和指南,请参阅:
脚本编写与核心
@references/rive-scripting-api.md - 完整的 Luau 脚本编写 API编辑器功能
@references/rive-editor-fundamentals.md - 界面、画板、形状、组件@references/rive-animation-mode.md - 时间轴、关键帧、缓动、动画混合@references/rive-state-machine.md - 状态、输入、过渡、监听器、图层@references/rive-constraints.md - IK、距离、变换、跟随路径约束@references/rive-layouts.md - 类 Flexbox 布局、N 切片、滚动@references/rive-manipulating-shapes.md - 骨骼、网格、裁剪、摇杆@references/rive-text.md - 字体、文本运行、修饰符、样式@references/rive-events.md - Rive 事件、音频事件、运行时监听@references/rive-data-binding.md - 视图模型、列表、运行时数据绑定Web 运行时
@references/rive-react-runtime.md - React/Next.js 集成@references/rive-web-runtime.md - 原生 JS、Canvas、WebGL、WASM移动端运行时
@references/rive-flutter-runtime.md - Flutter 组件和控制器@references/rive-mobile-runtimes.md - iOS(Swift)、Android(Kotlin)、React Native游戏引擎运行时
@references/rive-game-runtimes.md - Unity、Unreal Engine、Defold每周安装数
48
仓库
GitHub 星标数
5
首次出现
2026 年 1 月 25 日
安全审计
安装于
opencode37
claude-code37
gemini-cli35
codex35
cursor30
github-copilot27
This skill provides comprehensive knowledge for working with Rive, an interactive animation platform that enables creating and running interactive graphics across web, mobile, and game platforms.
Rive is a design and animation tool that produces lightweight, interactive graphics with a powerful runtime. Key capabilities:
Use this skill when:
import { useRive } from '@rive-app/react-canvas';
function MyAnimation() {
const { rive, RiveComponent } = useRive({
src: '/animation.riv',
stateMachines: 'MainStateMachine',
autoplay: true,
});
return <RiveComponent style={{ width: 400, height: 400 }} />;
}
const { rive, RiveComponent } = useRive({
src: '/animation.riv',
stateMachines: 'State Machine 1',
});
// Get and set inputs
const scrollInput = rive?.stateMachineInputs('State Machine 1')
?.find(i => i.name === 'scrollProgress');
// Update on scroll (0-100 range example)
scrollInput?.value = scrollProgress * 100;
Rive scripts use Luau (a Lua variant) and follow specific protocols. For detailed API reference, see @references/rive-scripting-api.md.
| Protocol | Purpose | Key Functions |
|---|---|---|
| Node | Custom drawing/rendering | init(), advance(seconds), draw(renderer) |
| Layout | Custom layout behaviors | measure(), resize(size) + Node functions |
| Converter | Data transformation | convert(input), |
-- Define script data type with inputs
type MyNode = {
color: Input<Color>,
speed: Input<number>,
path: Path,
paint: Paint,
}
function init(self: MyNode): boolean
self.path = Path.new()
self.paint = Paint.new()
self.paint.style = 'fill'
return true
end
function advance(self: MyNode, seconds: number): boolean
-- Animation logic here, called every frame
return true -- Return true to keep receiving advance calls
end
function update(self: MyNode)
-- Called when any input changes
end
function draw(self: MyNode, renderer: Renderer)
renderer:drawPath(self.path, self.paint)
end
return function(): Node<MyNode>
return {
init = init,
advance = advance,
update = update,
draw = draw,
color = Color.rgba(255, 255, 255, 255),
speed = 1.0,
path = late(),
paint = late(),
}
end
type MyLayout = {
spacing: Input<number>,
}
function measure(self: MyLayout): Vec2D
-- Return desired size (used when Fit type is Hug)
return Vec2D.xy(200, 100)
end
function resize(self: MyLayout, size: Vec2D)
-- Called when layout receives new size
print("New size:", size.x, size.y)
end
-- Include Node functions (init, advance, draw) as needed
return function(): Layout<MyLayout>
return {
measure = measure,
resize = resize,
spacing = 10,
}
end
type NumberToString = {}
function convert(self: NumberToString, input: DataInputs): DataOutput
local dv: DataValueString = DataValue.string()
if input:isNumber() then
dv.value = tostring((input :: DataValueNumber).value)
else
dv.value = ""
end
return dv
end
function reverseConvert(self: NumberToString, input: DataOutput): DataInputs
local dv: DataValueNumber = DataValue.number()
if input:isString() then
dv.value = tonumber((input :: DataValueString).value) or 0
end
return dv
end
return function(): Converter<NumberToString, DataValueNumber, DataValueString>
return {
convert = convert,
reverseConvert = reverseConvert,
}
end
type WaveEffect = {
amplitude: Input<number>,
frequency: Input<number>,
context: Context,
}
function init(self: WaveEffect, context: Context): boolean
self.context = context
return true
end
function update(self: WaveEffect, inPath: PathData): PathData
local path = Path.new()
-- Transform path geometry here
for i = 1, #inPath do
local cmd = inPath[i]
-- Process each PathCommand
end
return path
end
function advance(self: WaveEffect, seconds: number): boolean
-- Called each frame for animated effects
return true
end
return function(): PathEffect<WaveEffect>
return {
init = init,
update = update,
advance = advance,
amplitude = 10,
frequency = 1,
context = late(),
}
end
Define inputs to expose configurable properties in the Rive Editor:
type MyNode = {
-- Basic inputs
myNumber: Input<number>,
myColor: Input<Color>,
myString: string, -- Non-input, internal use only
-- View Model inputs
myViewModel: Input<Data.Character>,
-- Artboard inputs (for dynamic instantiation)
enemyTemplate: Input<Artboard<Data.Enemy>>,
}
-- Access input values
function init(self: MyNode): boolean
print("Number:", self.myNumber)
print("Color:", self.myColor)
print("ViewModel property:", self.myViewModel.health.value)
return true
end
-- Listen for changes
function init(self: MyNode): boolean
self.myNumber:addListener(function()
print("myNumber changed!")
end)
return true
end
-- Mark inputs assigned at runtime
return function(): Node<MyNode>
return {
init = init,
myNumber = 0,
myColor = Color.rgba(255, 255, 255, 255),
myString = "default",
myViewModel = late(), -- Assigned via Editor
enemyTemplate = late(),
}
end
Handle touch/mouse interactions:
function pointerDown(self: MyNode, event: PointerEvent)
print("Position:", event.position.x, event.position.y)
print("Pointer ID:", event.id) -- For multi-touch
event:hit() -- Mark as handled
end
function pointerMove(self: MyNode, event: PointerEvent)
-- Handle drag
event:hit()
end
function pointerUp(self: MyNode, event: PointerEvent)
event:hit()
end
function pointerExit(self: MyNode, event: PointerEvent)
event:hit()
end
return function(): Node<MyNode>
return {
pointerDown = pointerDown,
pointerMove = pointerMove,
pointerUp = pointerUp,
pointerExit = pointerExit,
}
end
Create artboard instances at runtime:
type Enemy = {
artboard: Artboard<Data.Enemy>,
position: Vec2D,
}
type GameScene = {
enemyTemplate: Input<Artboard<Data.Enemy>>,
enemies: { Enemy },
}
function createEnemy(self: GameScene, x: number, y: number)
local enemy = self.enemyTemplate:instance()
local entry: Enemy = {
artboard = enemy,
position = Vec2D.xy(x, y),
}
table.insert(self.enemies, entry)
end
function advance(self: GameScene, seconds: number): boolean
for _, enemy in self.enemies do
enemy.artboard:advance(seconds)
end
return true
end
function draw(self: GameScene, renderer: Renderer)
for _, enemy in self.enemies do
renderer:save()
renderer:transform(Mat2D.fromTranslate(enemy.position.x, enemy.position.y))
enemy.artboard:draw(renderer)
renderer:restore()
end
end
Access View Model from scripts:
function init(self: MyNode, context: Context): boolean
local vm = context:viewModel()
-- Get properties
local score = vm:getNumber('score')
local name = vm:getString('playerName')
-- Set values
if score then
score.value = 100
end
-- Listen for changes
if score then
score:addListener(function()
print("Score changed to:", score.value)
end)
end
-- Access nested view models
local settings = vm:getViewModel('settings')
local volume = settings:getNumber('volume')
return true
end
For complete API reference, see @references/rive-scripting-api.md.
local path = Path.new()
-- Drawing commands
path:moveTo(Vec2D.xy(0, 0))
path:lineTo(Vec2D.xy(100, 0))
path:quadTo(Vec2D.xy(150, 50), Vec2D.xy(100, 100))
path:cubicTo(Vec2D.xy(75, 150), Vec2D.xy(25, 150), Vec2D.xy(0, 100))
path:close()
-- Reset path for reuse
path:reset()
-- Measure path
local length = path:measure()
local contours = path:contours()
local paint = Paint.new()
paint.style = 'fill' -- or 'stroke'
paint.color = Color.rgba(255, 128, 0, 255)
paint.thickness = 3 -- For strokes
paint.cap = 'round' -- 'butt', 'round', 'square'
paint.join = 'round' -- 'miter', 'round', 'bevel'
paint.blendMode = 'srcOver'
-- Gradient fills
paint.gradient = Gradient.linear(
Vec2D.xy(0, 0),
Vec2D.xy(100, 100),
{ GradientStop.new(0, Color.hex('#FF0000')),
GradientStop.new(1, Color.hex('#0000FF')) }
)
function draw(self: MyNode, renderer: Renderer)
renderer:save()
-- Transform
renderer:transform(Mat2D.fromScale(2, 2))
renderer:transform(Mat2D.fromRotation(math.pi / 4))
renderer:transform(Mat2D.fromTranslate(50, 50))
-- Draw path
renderer:drawPath(self.path, self.paint)
-- Draw image
renderer:drawImage(self.image, ImageSampler.linear, 'srcOver', 1.0)
-- Clipping
renderer:clipPath(self.clipPath)
renderer:restore()
end
For detailed runtime API, see @references/rive-react-runtime.md.
# Recommended
npm install @rive-app/react-canvas
# Alternative options
npm install @rive-app/react-canvas-lite # Smaller, no Rive Text
npm install @rive-app/react-webgl # WebGL renderer
npm install @rive-app/react-webgl2 # Rive Renderer (WebGL2)
import { useRive, useStateMachineInput } from '@rive-app/react-canvas';
function Animation() {
const { rive, RiveComponent } = useRive({
src: '/animation.riv',
artboard: 'MainArtboard',
stateMachines: 'StateMachine1',
autoplay: true,
layout: new Layout({
fit: Fit.Contain,
alignment: Alignment.Center,
}),
});
// Playback control
const play = () => rive?.play();
const pause = () => rive?.pause();
const stop = () => rive?.stop();
return (
<RiveComponent
style={{ width: '100%', height: '100vh' }}
onMouseEnter={() => rive?.play()}
/>
);
}
function InteractiveAnimation() {
const { rive, RiveComponent } = useRive({
src: '/interactive.riv',
stateMachines: 'Controls',
autoplay: true,
});
useEffect(() => {
if (!rive) return;
const inputs = rive.stateMachineInputs('Controls');
// Number input
const progress = inputs?.find(i => i.name === 'progress');
if (progress) progress.value = 50;
// Boolean input
const isActive = inputs?.find(i => i.name === 'isActive');
if (isActive) isActive.value = true;
// Trigger input
const onClick = inputs?.find(i => i.name === 'onClick');
onClick?.fire();
}, [rive]);
return <RiveComponent />;
}
function ScrollAnimation() {
const { rive, RiveComponent } = useRive({
src: '/scroll-animation.riv',
stateMachines: 'ScrollMachine',
autoplay: true,
});
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!rive) return;
const progressInput = rive.stateMachineInputs('ScrollMachine')
?.find(i => i.name === 'scrollProgress');
const handleScroll = () => {
if (!containerRef.current || !progressInput) return;
const rect = containerRef.current.getBoundingClientRect();
const windowHeight = window.innerHeight;
// Calculate scroll progress (0-100)
const progress = Math.max(0, Math.min(100,
((windowHeight - rect.top) / (windowHeight + rect.height)) * 100
));
progressInput.value = progress;
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [rive]);
return (
<div ref={containerRef} style={{ height: '200vh' }}>
<div style={{ position: 'sticky', top: 0, height: '100vh' }}>
<RiveComponent style={{ width: '100%', height: '100%' }} />
</div>
</div>
);
}
function AnimationWithEvents() {
const { rive, RiveComponent } = useRive({
src: '/events.riv',
stateMachines: 'Main',
autoplay: true,
});
useEffect(() => {
if (!rive) return;
// Listen for Rive events
rive.on('statechange', (event) => {
console.log('State changed:', event.data);
});
rive.on('riveevent', (event) => {
console.log('Rive event:', event.data.name);
});
}, [rive]);
return <RiveComponent />;
}
local anim = artboard:animation('AnimationName')
-- Control
anim:advance(0.016) -- Advance by time in seconds
anim:setTime(1.5) -- Set time in seconds
anim:setTimeFrames(30) -- Set time in frames
anim:setTimePercentage(0.5) -- Set time as 0-1
-- Properties
local duration = anim.duration
local artboard = self.myArtboard:instance()
-- Properties
artboard.width = 400
artboard.height = 300
artboard.frameOrigin = true
-- Control
artboard:advance(seconds)
artboard:draw(renderer)
-- Access nodes and bounds
local node = artboard:node('NodeName')
local minPt, maxPt = artboard:bounds()
-- Pointer events
artboard:pointerDown(event)
artboard:pointerUp(event)
artboard:pointerMove(event)
init(), reuse in draw()scrollProgress input: Map scroll position to 0-100 rangesticky positioning: For scroll-triggered scenesautoplay and state machine name-- In scripts
print("Debug:", value)
-- Check Problems Panel in Rive Editor
-- Use Debug Panel for runtime inspection
For detailed API reference and guides, see:
Scripting & Core
@references/rive-scripting-api.md - Complete Luau scripting APIEditor Features
@references/rive-editor-fundamentals.md - Interface, artboards, shapes, components@references/rive-animation-mode.md - Timeline, keyframes, easing, animation mixing@references/rive-state-machine.md - States, inputs, transitions, listeners, layers@references/rive-constraints.md - IK, Distance, Transform, Follow Path constraints@references/rive-layouts.md - Flexbox-like layouts, N-Slicing, scrolling@references/rive-manipulating-shapes.md - Bones, meshes, clipping, joysticks@references/rive-text.md - Fonts, text runs, modifiers, styles@references/rive-events.md - Rive events, audio events, runtime listeningWeb Runtimes
@references/rive-react-runtime.md - React/Next.js integration@references/rive-web-runtime.md - Vanilla JS, Canvas, WebGL, WASMMobile Runtimes
@references/rive-flutter-runtime.md - Flutter widgets and controllers@references/rive-mobile-runtimes.md - iOS (Swift), Android (Kotlin), React NativeGame Engine Runtimes
@references/rive-game-runtimes.md - Unity, Unreal Engine, DefoldWeekly Installs
48
Repository
GitHub Stars
5
First Seen
Jan 25, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
opencode37
claude-code37
gemini-cli35
codex35
cursor30
github-copilot27
前端开发AI工具 | 5大专业能力构建生产级前端页面 | 设计工程与动效系统
868 周安装
reverseConvert(input)| PathEffect | Path modifications | init(), update(pathData), advance(seconds) |
| Test | Testing harnesses | Unit testing scripts |
@references/rive-data-binding.md - View Models, lists, runtime data binding