reanimated-skia-performance
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReanimated + Skia Performance
Reanimated + Skia 性能优化
Defaults
默认规范
- Keep animation state on the UI thread: ,
useSharedValue, worklets.useDerivedValue - Prefer Reanimated v4 declarative APIs; avoid legacy .
Animated - Prefer /
shared.get()overshared.set()in app code (React Compiler friendly).shared.value - Minimize JS↔UI crossings: avoid /
scheduleOnRNexcept for unavoidable side effects.runOnJS - For Skia, avoid per-frame React renders: pass s directly to Skia props/uniforms.
SharedValue
- 将动画状态保存在UI线程:、
useSharedValue、worklets。useDerivedValue - 优先使用Reanimated v4声明式API;避免使用旧版。
Animated - 在应用代码中优先使用/
shared.get()而非shared.set()(兼容React Compiler)。shared.value - 尽量减少JS↔UI线程交互:除非是不可避免的副作用,否则避免使用/
scheduleOnRN。runOnJS - 对于Skia,避免每帧触发React重渲染:直接将传递给Skia属性/统一变量。
SharedValue
Workflow
工作流程
- Define the effect: what animates, duration/curve, interrupt rules, and gesture input.
- Choose the renderer:
- Use Reanimated styles for transforms/opacity/layout.
- Use Skia for custom drawing, particles, gradients, runtime effects/shaders.
- Choose the primitive:
- Use Reanimated CSS transitions/animations for simple declarative style changes.
- Use for tweens,
withTimingfor physics,withSpringfor momentum.withDecay - Use Layout Animations for mount/unmount or layout changes.
- Implement a single data flow on the UI thread (no per frame).
setState - Do a perf pass (see ).
references/perf-checklist.md
- 定义效果:明确动画对象、时长/曲线、中断规则以及手势输入。
- 选择渲染器:
- 使用Reanimated样式处理变换/透明度/布局。
- 使用Skia处理自定义绘图、粒子效果、渐变、运行时效果/着色器。
- 选择原语:
- 使用Reanimated CSS过渡/动画处理简单的声明式样式变化。
- 使用实现补间动画,
withTiming实现物理动画,withSpring实现动量动画。withDecay - 使用布局动画处理挂载/卸载或布局变化。
- 在UI线程上实现单一数据流(避免每帧调用)。
setState - 执行性能检查(参见)。
references/perf-checklist.md
Patterns
实践模式
Shared values (React Compiler safe)
共享值(兼容React Compiler)
- Read:
progress.get() - Write:
progress.set(withTiming(1))
ts
import { useEffect } from 'react';
import { useSharedValue, withTiming } from 'react-native-reanimated';
const progress = useSharedValue(0);
useEffect(() => {
progress.set(withTiming(1, { duration: 400 }));
}, [progress]);- 读取:
progress.get() - 写入:
progress.set(withTiming(1))
ts
import { useEffect } from 'react';
import { useSharedValue, withTiming } from 'react-native-reanimated';
const progress = useSharedValue(0);
useEffect(() => {
progress.set(withTiming(1, { duration: 400 }));
}, [progress]);Reanimated v4 CSS transitions
Reanimated v4 CSS过渡
Use for state-driven style changes where you do not need bespoke worklets.
tsx
import Animated from 'react-native-reanimated';
<Animated.View
style={{
width: expanded ? 240 : 160,
opacity: enabled ? 1 : 0.6,
transitionProperty: ['width', 'opacity'],
transitionDuration: 220,
transitionTimingFunction: 'ease-in-out',
}}
/>适用于无需自定义worklets的状态驱动式样式变化。
tsx
import Animated from 'react-native-reanimated';
<Animated.View
style={{
width: expanded ? 240 : 160,
opacity: enabled ? 1 : 0.6,
transitionProperty: ['width', 'opacity'],
transitionDuration: 220,
transitionTimingFunction: 'ease-in-out',
}}
/>Reanimated v4 CSS animations (keyframes)
Reanimated v4 CSS动画(关键帧)
Use for keyframe-like sequences (pulses, wiggles, repeated loops) without writing custom worklets.
Supported settings:
- (keyframes object)
animationName animationDurationanimationDelayanimationTimingFunctionanimationDirectionanimationIterationCountanimationFillModeanimationPlayState
tsx
import Animated from 'react-native-reanimated';
const pulse = {
from: { transform: [{ scale: 1 }], opacity: 0.9 },
'50%': { transform: [{ scale: 1.06 }], opacity: 1 },
to: { transform: [{ scale: 1 }], opacity: 0.9 },
};
<Animated.View
style={{
animationName: pulse,
animationDuration: '900ms',
animationDelay: '80ms',
animationTimingFunction: 'ease-in-out',
animationDirection: 'alternate',
animationIterationCount: 'infinite',
animationFillMode: 'both',
animationPlayState: paused ? 'paused' : 'running',
}}
/>适用于无需编写自定义worklets的关键帧序列动画(脉冲、摆动、重复循环)。
支持的设置:
- (关键帧对象)
animationName animationDurationanimationDelayanimationTimingFunctionanimationDirectionanimationIterationCountanimationFillModeanimationPlayState
tsx
import Animated from 'react-native-reanimated';
const pulse = {
from: { transform: [{ scale: 1 }], opacity: 0.9 },
'50%': { transform: [{ scale: 1.06 }], opacity: 1 },
to: { transform: [{ scale: 1 }], opacity: 0.9 },
};
<Animated.View
style={{
animationName: pulse,
animationDuration: '900ms',
animationDelay: '80ms',
animationTimingFunction: 'ease-in-out',
animationDirection: 'alternate',
animationIterationCount: 'infinite',
animationFillMode: 'both',
animationPlayState: paused ? 'paused' : 'running',
}}
/>Dev-mode tuning panel (sliders)
开发模式调优面板(滑块)
Use sliders to tune animation configs or shader uniforms in .
__DEV__- Keep the tuning UI in a separate component to avoid re-rendering the animated scene.
- Write slider values into s via
SharedValue..set() - For shader tuning: feed s into uniforms via
SharedValue.useDerivedValue - For animation config tuning: store config params in s and read them when starting/restarting the animation.
SharedValue
See: .
references/dev-tuning.md在模式下使用滑块调优动画配置或着色器统一变量。
__DEV__- 将调优UI放在单独组件中,避免触发动画场景重渲染。
- 通过将滑块值写入
.set()。SharedValue - 着色器调优:通过将
useDerivedValue传入统一变量。SharedValue - 动画配置调优:将配置参数存储在中,启动/重启动画时读取。
SharedValue
参见:。
references/dev-tuning.mdGesture-driven animation (Gesture Builder API)
手势驱动动画(Gesture Builder API)
Update shared values in and drive visuals via animated styles.
onUpdatetsx
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, { useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
const x = useSharedValue(0);
const gesture = Gesture.Pan()
.onUpdate((e) => {
x.set(e.translationX);
})
.onEnd(() => {
x.set(withSpring(0));
});
const style = useAnimatedStyle(() => ({ transform: [{ translateX: x.get() }] }));
<GestureDetector gesture={gesture}>
<Animated.View style={style} />
</GestureDetector>;在中更新共享值,通过动画样式驱动视觉效果。
onUpdatetsx
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, { useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
const x = useSharedValue(0);
const gesture = Gesture.Pan()
.onUpdate((e) => {
x.set(e.translationX);
})
.onEnd(() => {
x.set(withSpring(0));
});
const style = useAnimatedStyle(() => ({ transform: [{ translateX: x.get() }] }));
<GestureDetector gesture={gesture}>
<Animated.View style={style} />
</GestureDetector>;Skia shader with animated uniforms
Skia着色器与动画统一变量
- Compile the runtime effect once ().
useMemo - Pass uniforms as a (Skia detects animated props by
SharedValue<Uniforms>).{ value: T } - Do not access in your app code; pass the
.valueobject.SharedValue
tsx
import { useMemo } from 'react';
import { Skia, Canvas, Fill, Paint, Shader, type Uniforms } from '@shopify/react-native-skia';
import { useDerivedValue } from 'react-native-reanimated';
import { useClock } from '@shopify/react-native-skia';
const sksl = `
uniform float2 u_resolution;
uniform float u_time;
half4 main(float2 xy) {
float2 uv = xy / u_resolution;
float v = 0.5 + 0.5 * sin(u_time * 0.002 + uv.x * 8.0);
return half4(v, v * 0.6, 1.0 - v, 1.0);
}
`;
const effect = useMemo(() => Skia.RuntimeEffect.Make(sksl), []);
if (!effect) return null;
const clock = useClock(); // ms since first frame
const uniforms = useDerivedValue<Uniforms>(() => ({
u_resolution: Skia.Point(width, height),
u_time: clock.get(),
}));
<Canvas style={{ width, height }}>
<Fill>
<Paint>
<Shader source={effect} uniforms={uniforms} />
</Paint>
</Fill>
</Canvas>;- 仅编译一次运行时效果(使用)。
useMemo - 将统一变量作为传递(Skia通过
SharedValue<Uniforms>检测动画属性)。{ value: T } - 不要在应用代码中访问;直接传递
.value对象。SharedValue
tsx
import { useMemo } from 'react';
import { Skia, Canvas, Fill, Paint, Shader, type Uniforms } from '@shopify/react-native-skia';
import { useDerivedValue } from 'react-native-reanimated';
import { useClock } from '@shopify/react-native-skia';
const sksl = `
uniform float2 u_resolution;
uniform float u_time;
half4 main(float2 xy) {
float2 uv = xy / u_resolution;
float v = 0.5 + 0.5 * sin(u_time * 0.002 + uv.x * 8.0);
return half4(v, v * 0.6, 1.0 - v, 1.0);
}
`;
const effect = useMemo(() => Skia.RuntimeEffect.Make(sksl), []);
if (!effect) return null;
const clock = useClock(); // ms since first frame
const uniforms = useDerivedValue<Uniforms>(() => ({
u_resolution: Skia.Point(width, height),
u_time: clock.get(),
}));
<Canvas style={{ width, height }}>
<Fill>
<Paint>
<Shader source={effect} uniforms={uniforms} />
</Paint>
</Fill>
</Canvas>;Common pitfalls
常见陷阱
- Do not read shared values in React render; read them in worklets (,
useAnimatedStyle).useDerivedValue - Do not call /
runOnJSinscheduleOnRNunless you must.onUpdate - Do not allocate big arrays/paths/images per frame; memoize Skia objects and update via animated props.
- For runtime effects, always provide every uniform declared in the shader; missing uniforms throw.
- 不要在React渲染中读取共享值;应在worklets(、
useAnimatedStyle)中读取。useDerivedValue - 除非必要,否则不要在中调用
onUpdate/runOnJS。scheduleOnRN - 不要每帧分配大数组/路径/图像;对Skia对象进行memoize,通过动画属性更新。
- 对于运行时效果,必须提供着色器中声明的所有统一变量;缺失统一变量会抛出错误。
References
参考资料
- Reanimated v4 patterns and repo conventions:
references/reanimated-v4.md - Skia Canvas + runtime effects/shaders patterns:
references/skia-shaders.md - Dev-mode tuning panels (sliders):
references/dev-tuning.md - Performance checklist + debugging:
references/perf-checklist.md
- Reanimated v4模式及仓库约定:
references/reanimated-v4.md - Skia Canvas + 运行时效果/着色器模式:
references/skia-shaders.md - 开发模式调优面板(滑块):
references/dev-tuning.md - 性能检查清单 + 调试:
references/perf-checklist.md