Reanimated Patterns Skill
This skill covers React Native Reanimated for performant animations.
When to Use
Use this skill when:
-
Implementing smooth animations
-
Gesture-based interactions
-
Complex animation sequences
-
Performance-critical animations
Core Principle
UI THREAD ANIMATIONS - Reanimated runs animations on the UI thread for 60fps.
Installation
npx expo install react-native-reanimated
Update babel.config.js:
module.exports = function(api) { api.cache(true); return { presets: ['babel-preset-expo'], plugins: ['react-native-reanimated/plugin'], }; };
Basic Concepts
Shared Values
import { useSharedValue } from 'react-native-reanimated';
// Shared values are synchronized between JS and UI threads const scale = useSharedValue(1); const opacity = useSharedValue(0); const translateX = useSharedValue(0);
// Update shared values scale.value = 2; opacity.value = 1;
Animated Styles
import Animated, { useSharedValue, useAnimatedStyle, } from 'react-native-reanimated';
function AnimatedBox(): React.ReactElement { const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }], }));
return ( <Animated.View style={[styles.box, animatedStyle]} /> ); }
Animation Functions
withSpring
import { withSpring } from 'react-native-reanimated';
// Spring animation (natural bounce) scale.value = withSpring(2, { damping: 10, stiffness: 100, mass: 1, });
withTiming
import { withTiming, Easing } from 'react-native-reanimated';
// Timed animation opacity.value = withTiming(1, { duration: 300, easing: Easing.bezier(0.25, 0.1, 0.25, 1), });
withDelay
import { withDelay, withSpring } from 'react-native-reanimated';
// Delayed animation scale.value = withDelay(500, withSpring(2));
withSequence
import { withSequence, withTiming } from 'react-native-reanimated';
// Sequential animations scale.value = withSequence( withTiming(1.2, { duration: 100 }), withTiming(0.9, { duration: 100 }), withTiming(1, { duration: 100 }) );
withRepeat
import { withRepeat, withTiming } from 'react-native-reanimated';
// Repeating animation rotation.value = withRepeat( withTiming(360, { duration: 1000 }), -1, // -1 for infinite false // reverse );
Common Animations
Fade In/Out
import Animated, { useSharedValue, useAnimatedStyle, withTiming, } from 'react-native-reanimated';
function FadeView({ visible }: { visible: boolean }): React.ReactElement { const opacity = useSharedValue(0);
useEffect(() => { opacity.value = withTiming(visible ? 1 : 0, { duration: 300 }); }, [visible]);
const animatedStyle = useAnimatedStyle(() => ({ opacity: opacity.value, }));
return ( <Animated.View style={animatedStyle}> {children} </Animated.View> ); }
Scale on Press
function ScaleButton({ onPress, children }: Props): React.ReactElement { const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }], }));
const handlePressIn = () => { scale.value = withSpring(0.95); };
const handlePressOut = () => { scale.value = withSpring(1); };
return ( <TouchableOpacity onPressIn={handlePressIn} onPressOut={handlePressOut} onPress={onPress} activeOpacity={1} > <Animated.View style={animatedStyle}> {children} </Animated.View> </TouchableOpacity> ); }
Slide In
import { Dimensions } from 'react-native';
const { width } = Dimensions.get('window');
function SlideIn({ visible }: { visible: boolean }): React.ReactElement { const translateX = useSharedValue(width);
useEffect(() => { translateX.value = withSpring(visible ? 0 : width); }, [visible]);
const animatedStyle = useAnimatedStyle(() => ({ transform: [{ translateX: translateX.value }], }));
return ( <Animated.View style={[styles.panel, animatedStyle]}> {children} </Animated.View> ); }
Entering/Exiting Animations
import Animated, { FadeIn, FadeOut, SlideInRight, SlideOutLeft, ZoomIn, BounceIn, } from 'react-native-reanimated';
// Fade <Animated.View entering={FadeIn.duration(300)} exiting={FadeOut.duration(200)}>
// Slide <Animated.View entering={SlideInRight} exiting={SlideOutLeft}>
// Zoom <Animated.View entering={ZoomIn.springify()}>
// Bounce <Animated.View entering={BounceIn.delay(200)}>
// Custom <Animated.View entering={FadeIn.duration(500).delay(100).springify()} exiting={FadeOut.duration(300)}
Layout Animations
import Animated, { Layout } from 'react-native-reanimated';
// Animate layout changes <Animated.View layout={Layout.springify()}> {items.map((item) => ( <Animated.View key={item.id} entering={FadeIn} exiting={FadeOut} layout={Layout} > <Text>{item.name}</Text> </Animated.View> ))} </Animated.View>
Gesture Handler Integration
import { GestureDetector, Gesture } from 'react-native-gesture-handler'; import Animated, { useSharedValue, useAnimatedStyle, withSpring, } from 'react-native-reanimated';
function DraggableBox(): React.ReactElement { const translateX = useSharedValue(0); const translateY = useSharedValue(0);
const gesture = Gesture.Pan() .onUpdate((event) => { translateX.value = event.translationX; translateY.value = event.translationY; }) .onEnd(() => { translateX.value = withSpring(0); translateY.value = withSpring(0); });
const animatedStyle = useAnimatedStyle(() => ({ transform: [ { translateX: translateX.value }, { translateY: translateY.value }, ], }));
return ( <GestureDetector gesture={gesture}> <Animated.View style={[styles.box, animatedStyle]} /> </GestureDetector> ); }
Interpolation
import { interpolate, Extrapolation } from 'react-native-reanimated';
const animatedStyle = useAnimatedStyle(() => { const scale = interpolate( scrollY.value, [0, 100], [1, 0.5], Extrapolation.CLAMP );
const opacity = interpolate( scrollY.value, [0, 50, 100], [1, 0.5, 0] );
return { transform: [{ scale }], opacity, }; });
Worklets
import { runOnJS } from 'react-native-reanimated';
// Call JS function from worklet const updateState = (value: number) => { setState(value); };
const animatedStyle = useAnimatedStyle(() => { if (position.value > 100) { runOnJS(updateState)(position.value); } return { transform: [{ translateX: position.value }] }; });
Notes
-
All animation logic runs on UI thread
-
Use worklets ('worklet' directive) for custom functions
-
Use runOnJS to call JS functions from worklets
-
Test animations on real devices
-
Reanimated is compatible with Expo