Back to Posts
React Native Animation Mastery
By Lumina Software•
react-nativeuimobile-developmentperformance
React Native Animation Mastery
Animations make apps feel polished and responsive. In React Native, you have multiple options for animations, each with different strengths. Here's how to master animations in React Native.
Animation Libraries
1. Animated API (Built-in)
Good for simple animations:
import { Animated } from 'react-native';
function FadeIn() {
const fadeAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 1000,
useNativeDriver: true, // ✅ Always use this
}).start();
}, []);
return (
<Animated.View style={{ opacity: fadeAnim }}>
<Text>Fading in</Text>
</Animated.View>
);
}
2. Reanimated 3 (Recommended)
Best for complex, performant animations:
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming,
} from 'react-native-reanimated';
function SpringAnimation() {
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
const handlePress = () => {
scale.value = withSpring(1.2, {
damping: 10,
stiffness: 100,
});
};
return (
<Animated.View style={animatedStyle}>
<Pressable onPress={handlePress}>
<Text>Press me</Text>
</Pressable>
</Animated.View>
);
}
3. React Native Gesture Handler
For gesture-based animations:
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
} from 'react-native-reanimated';
function DraggableCard() {
const translateX = useSharedValue(0);
const translateY = useSharedValue(0);
const pan = Gesture.Pan()
.onUpdate((e) => {
translateX.value = e.translationX;
translateY.value = e.translationY;
})
.onEnd(() => {
translateX.value = withSpring(0);
translateY.value = withSpring(0);
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{ translateX: translateX.value },
{ translateY: translateY.value },
],
}));
return (
<GestureDetector gesture={pan}>
<Animated.View style={animatedStyle}>
<Text>Drag me</Text>
</Animated.View>
</GestureDetector>
);
}
Common Animation Patterns
1. Fade In/Out
function FadeInOut({ visible }: { visible: boolean }) {
const opacity = useSharedValue(visible ? 1 : 0);
useEffect(() => {
opacity.value = withTiming(visible ? 1 : 0, { duration: 300 });
}, [visible]);
const animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
}));
return (
<Animated.View style={animatedStyle}>
<Text>Content</Text>
</Animated.View>
);
}
2. Slide Animations
function SlideIn({ from }: { from: 'left' | 'right' | 'top' | 'bottom' }) {
const translate = useSharedValue(from === 'left' ? -100 : 100);
useEffect(() => {
translate.value = withSpring(0);
}, []);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: translate.value }],
}));
return (
<Animated.View style={animatedStyle}>
<Text>Sliding in</Text>
</Animated.View>
);
}
3. Scale Animations
function ScaleOnPress() {
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 (
<Pressable onPressIn={handlePressIn} onPressOut={handlePressOut}>
<Animated.View style={animatedStyle}>
<Text>Press me</Text>
</Animated.View>
</Pressable>
);
}
4. Rotation
function RotatingIcon() {
const rotation = useSharedValue(0);
useEffect(() => {
rotation.value = withRepeat(
withTiming(360, { duration: 2000 }),
-1, // Infinite
false
);
}, []);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ rotate: `${rotation.value}deg` }],
}));
return (
<Animated.View style={animatedStyle}>
<Icon name="spinner" />
</Animated.View>
);
}
Advanced Patterns
1. Shared Element Transitions
import { createSharedElementStackNavigator } from 'react-navigation-shared-element';
const Stack = createSharedElementStackNavigator();
function SharedElementTransition() {
return (
<Stack.Navigator>
<Stack.Screen
name="List"
component={ListScreen}
sharedElementsConfig={() => ['item-1']}
/>
<Stack.Screen
name="Detail"
component={DetailScreen}
sharedElementsConfig={() => ['item-1']}
/>
</Stack.Navigator>
);
}
2. Layout Animations
import { Layout } from 'react-native-reanimated';
function AnimatedList({ items }: { items: Item[] }) {
return (
<FlatList
data={items}
renderItem={({ item }) => (
<Animated.View layout={Layout.springify()}>
<Text>{item.title}</Text>
</Animated.View>
)}
/>
);
}
3. Staggered Animations
function StaggeredList({ items }: { items: Item[] }) {
return (
<View>
{items.map((item, index) => (
<AnimatedItem
key={item.id}
item={item}
delay={index * 100} // Stagger by 100ms
/>
))}
</View>
);
}
function AnimatedItem({ item, delay }: { item: Item; delay: number }) {
const opacity = useSharedValue(0);
const translateY = useSharedValue(50);
useEffect(() => {
setTimeout(() => {
opacity.value = withTiming(1, { duration: 300 });
translateY.value = withSpring(0);
}, delay);
}, [delay]);
const animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
transform: [{ translateY: translateY.value }],
}));
return (
<Animated.View style={animatedStyle}>
<Text>{item.title}</Text>
</Animated.View>
);
}
Performance Optimization
1. Use Native Driver
Always use useNativeDriver: true for Animated API:
// ✅ Good
Animated.timing(value, {
toValue: 1,
useNativeDriver: true, // Runs on UI thread
});
// ❌ Bad
Animated.timing(value, {
toValue: 1,
useNativeDriver: false, // Runs on JS thread
});
2. Animate Transform Properties
Transform properties are most performant:
// ✅ Good: Transform properties
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{ translateX: x.value },
{ translateY: y.value },
{ scale: scale.value },
],
}));
// ❌ Bad: Layout properties
const animatedStyle = useAnimatedStyle(() => ({
left: x.value, // Triggers layout recalculation
top: y.value,
}));
3. Avoid Animating Layout Properties
// ❌ Bad: Animating width/height
const animatedStyle = useAnimatedStyle(() => ({
width: width.value, // Expensive
height: height.value,
}));
// ✅ Good: Use scale instead
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
Gesture-Based Animations
Swipe to Dismiss
function SwipeableCard({ onDismiss }: { onDismiss: () => void }) {
const translateX = useSharedValue(0);
const pan = Gesture.Pan()
.onUpdate((e) => {
translateX.value = e.translationX;
})
.onEnd((e) => {
if (Math.abs(e.translationX) > 100) {
// Dismiss
translateX.value = withTiming(
e.translationX > 0 ? 500 : -500,
{ duration: 200 },
() => {
runOnJS(onDismiss)();
}
);
} else {
// Snap back
translateX.value = withSpring(0);
}
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: translateX.value }],
opacity: 1 - Math.abs(translateX.value) / 200,
}));
return (
<GestureDetector gesture={pan}>
<Animated.View style={animatedStyle}>
<Text>Swipe to dismiss</Text>
</Animated.View>
</GestureDetector>
);
}
Pull to Refresh
function PullToRefresh({ onRefresh }: { onRefresh: () => void }) {
const translateY = useSharedValue(0);
const isRefreshing = useSharedValue(false);
const pan = Gesture.Pan()
.onUpdate((e) => {
if (e.translationY > 0) {
translateY.value = e.translationY;
}
})
.onEnd((e) => {
if (e.translationY > 100) {
isRefreshing.value = true;
translateY.value = withSpring(50);
runOnJS(onRefresh)();
setTimeout(() => {
isRefreshing.value = false;
translateY.value = withSpring(0);
}, 1000);
} else {
translateY.value = withSpring(0);
}
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateY: translateY.value }],
}));
return (
<GestureDetector gesture={pan}>
<Animated.View style={animatedStyle}>
{isRefreshing.value && <ActivityIndicator />}
<Text>Pull to refresh</Text>
</Animated.View>
</GestureDetector>
);
}
Animation Utilities
Custom Hooks
function useFadeIn(duration = 300) {
const opacity = useSharedValue(0);
useEffect(() => {
opacity.value = withTiming(1, { duration });
}, [duration]);
return useAnimatedStyle(() => ({
opacity: opacity.value,
}));
}
// Usage
function Component() {
const fadeStyle = useFadeIn(500);
return <Animated.View style={fadeStyle}>Content</Animated.View>;
}
Animation Presets
const animations = {
fadeIn: (duration = 300) => ({
opacity: withTiming(1, { duration }),
}),
slideUp: (duration = 300) => ({
transform: [{ translateY: withTiming(0, { duration }) }],
}),
scaleIn: (duration = 300) => ({
transform: [{ scale: withSpring(1, { damping: 10 }) }],
}),
};
Best Practices
- Use Reanimated 3: Best performance and features
- Animate transforms: Most performant properties
- Use native driver: Always for Animated API
- Keep animations short: 200-500ms for most interactions
- Respect user preferences: Honor reduced motion settings
- Test on devices: Simulators don't show real performance
- Avoid over-animation: Don't animate everything
Accessibility
import { useReducedMotion } from 'react-native-reanimated';
function AccessibleAnimation() {
const reducedMotion = useReducedMotion();
const scale = useSharedValue(1);
useEffect(() => {
if (!reducedMotion) {
scale.value = withSpring(1.1);
}
}, [reducedMotion]);
// ... rest of component
}
Conclusion
Mastering React Native animations requires:
- Right library: Reanimated 3 for complex animations
- Performance: Animate transforms, use native driver
- Patterns: Reusable animation patterns
- Gestures: Gesture-based interactions
- Accessibility: Respect user preferences
Great animations make apps feel polished and responsive. Use these techniques to create delightful user experiences.
