React Native Animations
Animations make your app feel alive and professional. A loading spinner, a sliding menu, a button that bounces when tapped — these small movements guide attention, communicate state changes, and create delight. React Native provides two animation systems: the built-in Animated API and the more powerful react-native-reanimated library.
The Animated API — Built-In
The Animated API works by creating special animated values that track a number over time. React Native reads these values to update styles without re-rendering the JavaScript component.
Animated Value Drives Visual Change ───────────────────────────────────────────────────────── opacityAnim: 0→1 opacity fade in slideAnim: -100→0 translateX slide from left scaleAnim: 0.5→1 scale grow from small rotateAnim: 0→360 rotate spin
Fade In Animation
import { Animated, useRef, useEffect } from 'react';
import { View, Text } from 'react-native';
export default function FadeInCard() {
const opacity = useRef(new Animated.Value(0)).current;
// starts at 0 (invisible)
useEffect(() => {
Animated.timing(opacity, {
toValue: 1, // animate to fully visible
duration: 800, // takes 800 milliseconds
useNativeDriver: true, // runs on native thread — smoother
}).start();
}, []);
return (
<Animated.View style={{ opacity, padding: 20, backgroundColor: '#007AFF', borderRadius: 12 }}>
<Text style={{ color: 'white' }}>I faded in!</Text>
</Animated.View>
);
}
Timeline: t=0ms opacity=0.0 → invisible t=200ms opacity=0.25 t=400ms opacity=0.50 t=600ms opacity=0.75 t=800ms opacity=1.0 → fully visible
Three Types of Animated Animations
timing — Smooth, Linear or Eased
Animated.timing(value, {
toValue: 1,
duration: 500,
easing: Easing.ease, // accelerate then decelerate
useNativeDriver: true,
}).start();
spring — Bouncy Physics
Animated.spring(value, {
toValue: 1,
damping: 10, // how quickly bouncing settles
stiffness: 100, // how stiff the spring is
useNativeDriver: true,
}).start();
decay — Momentum and Slowdown
Animated.decay(value, {
velocity: 0.5, // starting speed
deceleration: 0.997,
useNativeDriver: true,
}).start();
Slide-In from Bottom
export default function SlideUpPanel() {
const translateY = useRef(new Animated.Value(300)).current;
// 300 = starts 300 points below visible area
useEffect(() => {
Animated.spring(translateY, {
toValue: 0, // slide to its natural position
damping: 15,
stiffness: 120,
useNativeDriver: true,
}).start();
}, []);
return (
<Animated.View style={{
transform: [{ translateY }],
position: 'absolute', bottom: 0, left: 0, right: 0,
backgroundColor: 'white', padding: 20, borderTopLeftRadius: 20, borderTopRightRadius: 20,
}}>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>Slide-Up Panel</Text>
</Animated.View>
);
}
Before animation: After animation: ┌──────────────────┐ ┌──────────────────┐ │ │ │ │ │ │ │ │ │ │ ├──────────────────┤ │ │ │ Slide-Up Panel │ │ │ │ │ └──────────────────┘ └──────────────────┘ panel below screen panel visible at bottom
Sequence and Parallel Animations
Run multiple animations one after another with Animated.sequence, or at the same time with Animated.parallel.
const opacity = useRef(new Animated.Value(0)).current;
const translateY = useRef(new Animated.Value(20)).current;
// Parallel: fade in AND slide up at the same time
Animated.parallel([
Animated.timing(opacity, { toValue: 1, duration: 400, useNativeDriver: true }),
Animated.timing(translateY, { toValue: 0, duration: 400, useNativeDriver: true }),
]).start();
// Sequence: first fade in, THEN slide
Animated.sequence([
Animated.timing(opacity, { toValue: 1, duration: 300, useNativeDriver: true }),
Animated.timing(translateY, { toValue: 0, duration: 300, useNativeDriver: true }),
]).start();
Looping Animation — Pulse Effect
const scale = useRef(new Animated.Value(1)).current;
useEffect(() => {
Animated.loop(
Animated.sequence([
Animated.timing(scale, { toValue: 1.1, duration: 500, useNativeDriver: true }),
Animated.timing(scale, { toValue: 1.0, duration: 500, useNativeDriver: true }),
])
).start();
}, []);
<Animated.View style={{ transform: [{ scale }], ... }}>
<Text>🔴 Live</Text>
</Animated.View>
Interpolation — Map One Range to Another
Interpolation lets one animated value drive multiple style properties with different ranges.
const scrollY = useRef(new Animated.Value(0)).current;
// As user scrolls from 0 to 100, header shrinks from 80 to 50
const headerHeight = scrollY.interpolate({
inputRange: [0, 100],
outputRange: [80, 50],
extrapolate: 'clamp', // don't go below 50 or above 80
});
// As user scrolls from 0 to 100, header text fades out
const headerOpacity = scrollY.interpolate({
inputRange: [0, 80],
outputRange: [1, 0],
extrapolate: 'clamp',
});
Scroll: 0 Scroll: 100 ┌─────────────────────┐ ┌─────────────────────┐ │ Large Header │ │ Small Header │ │ (80px, visible) │ │ (50px, faded) │ ├─────────────────────┤ ├─────────────────────┤ │ Content │ │ Content │ └─────────────────────┘ └─────────────────────┘
react-native-reanimated — High-Performance Animations
react-native-reanimated runs entirely on the native thread. It handles gesture-driven animations without any JavaScript delay between the finger and the animation.
npx expo install react-native-reanimated
import Animated, { useSharedValue, withSpring, useAnimatedStyle } from 'react-native-reanimated';
export default function BouncingBox() {
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
function onPress() {
scale.value = withSpring(1.2, { damping: 4 }, () => {
scale.value = withSpring(1); // bounce back
});
}
return (
<Pressable onPress={onPress}>
<Animated.View style={[{ width: 80, height: 80, backgroundColor: '#007AFF', borderRadius: 12 }, animatedStyle]} />
</Pressable>
);
}
When to Use Which System
Animated API (built-in) react-native-reanimated ────────────────────────────────────────────────────────────── Simple fade, slide, scale Complex gesture-driven animations One-shot animations Swipe cards, drag handles, stories No extra install needed Best performance for heavy animations Good for beginners Production apps with rich UI
Summary
Use Animated.Value with Animated.timing or Animated.spring for straightforward animations. Always set useNativeDriver: true for smooth performance. Use Animated.parallel and Animated.sequence to compose multiple animations. Use interpolate to link one animated value to multiple style changes. Choose react-native-reanimated for gesture-driven or very complex animation scenarios.
