# React Native Reanimated 3 Patterns ## Core Concepts ### Shared Values and Animated Styles ```typescript import Animated, { useSharedValue, useAnimatedStyle, withSpring, withTiming, withDelay, withSequence, withRepeat, Easing, } from 'react-native-reanimated'; function BasicAnimations() { // Shared value - can be modified from JS or UI thread const opacity = useSharedValue(0); const scale = useSharedValue(1); const rotation = useSharedValue(0); // Animated style - runs on UI thread const animatedStyle = useAnimatedStyle(() => ({ opacity: opacity.value, transform: [ { scale: scale.value }, { rotate: `${rotation.value}deg` }, ], })); const animate = () => { // Spring animation scale.value = withSpring(1.2, { damping: 10, stiffness: 100, }); // Timing animation with easing opacity.value = withTiming(1, { duration: 500, easing: Easing.bezier(0.25, 0.1, 0.25, 1), }); // Sequence of animations rotation.value = withSequence( withTiming(15, { duration: 100 }), withTiming(-15, { duration: 100 }), withTiming(0, { duration: 100 }) ); }; return ( ); } ``` ### Animation Callbacks ```typescript import { runOnJS, runOnUI } from 'react-native-reanimated'; function AnimationWithCallbacks() { const translateX = useSharedValue(0); const [status, setStatus] = useState('idle'); const updateStatus = (newStatus: string) => { setStatus(newStatus); }; const animate = () => { translateX.value = withTiming( 200, { duration: 1000 }, (finished) => { 'worklet'; if (finished) { // Call JS function from worklet runOnJS(updateStatus)('completed'); } } ); }; return ( ({ transform: [{ translateX: translateX.value }], })), ]} /> ); } ``` ## Gesture Handler Integration ### Pan Gesture ```typescript import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import Animated, { useSharedValue, useAnimatedStyle, withSpring, clamp, } from 'react-native-reanimated'; function DraggableBox() { const translateX = useSharedValue(0); const translateY = useSharedValue(0); const context = useSharedValue({ x: 0, y: 0 }); const panGesture = Gesture.Pan() .onStart(() => { context.value = { x: translateX.value, y: translateY.value }; }) .onUpdate((event) => { translateX.value = event.translationX + context.value.x; translateY.value = event.translationY + context.value.y; }) .onEnd((event) => { // Apply velocity decay translateX.value = withSpring( clamp(translateX.value + event.velocityX / 10, -100, 100) ); translateY.value = withSpring( clamp(translateY.value + event.velocityY / 10, -100, 100) ); }); const animatedStyle = useAnimatedStyle(() => ({ transform: [ { translateX: translateX.value }, { translateY: translateY.value }, ], })); return ( ); } ``` ### Pinch and Rotate Gestures ```typescript function ZoomableImage() { const scale = useSharedValue(1); const rotation = useSharedValue(0); const savedScale = useSharedValue(1); const savedRotation = useSharedValue(0); const pinchGesture = Gesture.Pinch() .onUpdate((event) => { scale.value = savedScale.value * event.scale; }) .onEnd(() => { savedScale.value = scale.value; // Snap back if too small if (scale.value < 1) { scale.value = withSpring(1); savedScale.value = 1; } }); const rotateGesture = Gesture.Rotation() .onUpdate((event) => { rotation.value = savedRotation.value + event.rotation; }) .onEnd(() => { savedRotation.value = rotation.value; }); const composed = Gesture.Simultaneous(pinchGesture, rotateGesture); const animatedStyle = useAnimatedStyle(() => ({ transform: [ { scale: scale.value }, { rotate: `${rotation.value}rad` }, ], })); return ( ); } ``` ### Tap Gesture with Feedback ```typescript function TappableCard({ onPress, children }: TappableCardProps) { const scale = useSharedValue(1); const opacity = useSharedValue(1); const tapGesture = Gesture.Tap() .onBegin(() => { scale.value = withSpring(0.97); opacity.value = withTiming(0.8, { duration: 100 }); }) .onFinalize(() => { scale.value = withSpring(1); opacity.value = withTiming(1, { duration: 100 }); }) .onEnd(() => { runOnJS(onPress)(); }); const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }], opacity: opacity.value, })); return ( {children} ); } ``` ## Common Animation Patterns ### Fade In/Out ```typescript function FadeInView({ visible, children }: FadeInViewProps) { const opacity = useSharedValue(0); useEffect(() => { opacity.value = withTiming(visible ? 1 : 0, { duration: 300 }); }, [visible]); const animatedStyle = useAnimatedStyle(() => ({ opacity: opacity.value, display: opacity.value === 0 ? 'none' : 'flex', })); return ( {children} ); } ``` ### Slide In/Out ```typescript function SlideInView({ visible, direction = 'right', children }) { const translateX = useSharedValue(direction === 'right' ? 100 : -100); const opacity = useSharedValue(0); useEffect(() => { if (visible) { translateX.value = withSpring(0); opacity.value = withTiming(1); } else { translateX.value = withSpring(direction === 'right' ? 100 : -100); opacity.value = withTiming(0); } }, [visible, direction]); const animatedStyle = useAnimatedStyle(() => ({ transform: [{ translateX: translateX.value }], opacity: opacity.value, })); return ( {children} ); } ``` ### Staggered List Animation ```typescript function StaggeredList({ items }: { items: Item[] }) { return ( item.id} renderItem={({ item, index }) => ( )} /> ); } function StaggeredItem({ item, index }: { item: Item; index: number }) { const translateY = useSharedValue(50); const opacity = useSharedValue(0); useEffect(() => { translateY.value = withDelay( index * 100, withSpring(0, { damping: 15 }) ); opacity.value = withDelay( index * 100, withTiming(1, { duration: 300 }) ); }, [index]); const animatedStyle = useAnimatedStyle(() => ({ transform: [{ translateY: translateY.value }], opacity: opacity.value, })); return ( {item.title} ); } ``` ### Pulse Animation ```typescript function PulseView({ children }: { children: React.ReactNode }) { const scale = useSharedValue(1); useEffect(() => { scale.value = withRepeat( withSequence( withTiming(1.05, { duration: 500 }), withTiming(1, { duration: 500 }) ), -1, // infinite false // no reverse ); return () => { cancelAnimation(scale); }; }, []); const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }], })); return ( {children} ); } ``` ### Shake Animation ```typescript function ShakeView({ trigger, children }) { const translateX = useSharedValue(0); useEffect(() => { if (trigger) { translateX.value = withSequence( withTiming(-10, { duration: 50 }), withTiming(10, { duration: 50 }), withTiming(-10, { duration: 50 }), withTiming(10, { duration: 50 }), withTiming(0, { duration: 50 }) ); } }, [trigger]); const animatedStyle = useAnimatedStyle(() => ({ transform: [{ translateX: translateX.value }], })); return ( {children} ); } ``` ## Advanced Patterns ### Interpolation ```typescript import { interpolate, Extrapolation } from 'react-native-reanimated'; function ParallaxHeader() { const scrollY = useSharedValue(0); const headerStyle = useAnimatedStyle(() => { const height = interpolate( scrollY.value, [0, 200], [300, 100], Extrapolation.CLAMP ); const opacity = interpolate( scrollY.value, [0, 150, 200], [1, 0.5, 0], Extrapolation.CLAMP ); const translateY = interpolate( scrollY.value, [0, 200], [0, -50], Extrapolation.CLAMP ); return { height, opacity, transform: [{ translateY }], }; }); return ( Header { scrollY.value = event.contentOffset.y; }, })} scrollEventThrottle={16} > {/* Content */} ); } ``` ### Color Interpolation ```typescript import { interpolateColor } from 'react-native-reanimated'; function ColorTransition() { const progress = useSharedValue(0); const animatedStyle = useAnimatedStyle(() => { const backgroundColor = interpolateColor( progress.value, [0, 0.5, 1], ['#6366f1', '#8b5cf6', '#ec4899'] ); return { backgroundColor }; }); const toggleColor = () => { progress.value = withTiming(progress.value === 0 ? 1 : 0, { duration: 1000, }); }; return ( ); } ``` ### Derived Values ```typescript import { useDerivedValue } from 'react-native-reanimated'; function DerivedValueExample() { const x = useSharedValue(0); const y = useSharedValue(0); // Derived value computed from other shared values const distance = useDerivedValue(() => { return Math.sqrt(x.value ** 2 + y.value ** 2); }); const angle = useDerivedValue(() => { return Math.atan2(y.value, x.value) * (180 / Math.PI); }); const animatedStyle = useAnimatedStyle(() => ({ transform: [ { translateX: x.value }, { translateY: y.value }, { rotate: `${angle.value}deg` }, ], opacity: interpolate(distance.value, [0, 200], [1, 0.5]), })); return ( ); } ``` ### Layout Animations ```typescript import Animated, { Layout, FadeIn, FadeOut, SlideInLeft, SlideOutRight, ZoomIn, ZoomOut, } from 'react-native-reanimated'; function AnimatedList() { const [items, setItems] = useState([1, 2, 3, 4, 5]); const addItem = () => { setItems([...items, items.length + 1]); }; const removeItem = (id: number) => { setItems(items.filter((item) => item !== id)); }; return (