--- name: reanimated-skia-performance description: "Write and review high-performance React Native animations and 2D graphics using react-native-reanimated (v4+) and @shopify/react-native-skia (Canvas scenes, runtime effects/shaders). Use for: gesture-driven interactions, spring/timing transitions, layout/mount animations, Reanimated CSS transitions/animations, Skia drawings, animated shader uniforms, path/vector interpolation, dev-mode tuning panels (sliders), and diagnosing animation jank (JS thread stalls, excessive re-renders, per-frame allocations)." --- # Reanimated + Skia Performance ## Defaults - Keep animation state on the UI thread: `useSharedValue`, `useDerivedValue`, worklets. - Prefer Reanimated v4 declarative APIs; avoid legacy `Animated`. - Prefer `shared.get()` / `shared.set()` over `shared.value` in app code (React Compiler friendly). - Minimize JS↔UI crossings: avoid `scheduleOnRN`/`runOnJS` except for unavoidable side effects. - For Skia, avoid per-frame React renders: pass `SharedValue`s directly to Skia props/uniforms. ## Workflow 1. Define the effect: what animates, duration/curve, interrupt rules, and gesture input. 2. Choose the renderer: - Use Reanimated styles for transforms/opacity/layout. - Use Skia for custom drawing, particles, gradients, runtime effects/shaders. 3. Choose the primitive: - Use Reanimated CSS transitions/animations for simple declarative style changes. - Use `withTiming` for tweens, `withSpring` for physics, `withDecay` for momentum. - Use Layout Animations for mount/unmount or layout changes. 4. Implement a single data flow on the UI thread (no `setState` per frame). 5. Do a perf pass (see `references/perf-checklist.md`). ## Patterns ### Shared values (React Compiler safe) - 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]); ``` ### Reanimated v4 CSS transitions Use for state-driven style changes where you do not need bespoke worklets. ```tsx import Animated from 'react-native-reanimated'; ``` ### Reanimated v4 CSS animations (keyframes) Use for keyframe-like sequences (pulses, wiggles, repeated loops) without writing custom worklets. Supported settings: - `animationName` (keyframes object) - `animationDuration` - `animationDelay` - `animationTimingFunction` - `animationDirection` - `animationIterationCount` - `animationFillMode` - `animationPlayState` ```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 }, }; ``` ### 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 `SharedValue`s via `.set()`. - For shader tuning: feed `SharedValue`s into uniforms via `useDerivedValue`. - For animation config tuning: store config params in `SharedValue`s and read them when starting/restarting the animation. See: `references/dev-tuning.md`. ### Gesture-driven animation (Gesture Builder API) Update shared values in `onUpdate` and drive visuals via animated styles. ```tsx 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() }] })); ; ``` ### Skia shader with animated uniforms - Compile the runtime effect once (`useMemo`). - Pass uniforms as a `SharedValue` (Skia detects animated props by `{ value: T }`). - Do not access `.value` in your app code; pass the `SharedValue` object. ```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(() => ({ u_resolution: Skia.Point(width, height), u_time: clock.get(), })); ; ``` ## Common pitfalls - Do not read shared values in React render; read them in worklets (`useAnimatedStyle`, `useDerivedValue`). - Do not call `runOnJS`/`scheduleOnRN` in `onUpdate` unless you must. - 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. ## 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`