Creating fluid, native-feeling animations is essential for delivering exceptional mobile experiences. This guide explores the latest React Native animation techniques and libraries that will help you create stunning 60+ FPS animations in 2025.
Modern React Native Animation Landscape
The React Native animation ecosystem has evolved dramatically in recent years. The emergence of libraries that leverage the native thread has transformed what's possible, moving beyond the performance limitations of the JavaScript thread. These advancements enable animations that feel truly native, maintaining 60+ FPS even on mid-range devices.
The evolution has been driven by three key technologies: Reanimated 3's worklet system for running animations directly on the UI thread, React Native Skia's direct GPU rendering capabilities, and improved gesture systems that eliminate the round-trip latency from the JS thread. Together, these technologies enable animations that were previously only possible in fully native applications.
1. Reanimated 3 - The Animation Powerhouse
Worklets and the UI Thread
Reanimated 3 uses JavaScript worklets that run directly on the UI thread, eliminating the bridge communication overhead that previously caused janky animations. This architecture enables truly native-feeling interactions with direct control over animated values.
Worklets are tiny JavaScript functions that get compiled to run on the UI thread. By keeping all animation logic on the same thread as rendering, Reanimated eliminates the main performance bottleneck in React Native animations.
import { useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
function AnimatedCard() {
// Shared values are accessible from both JS and UI threads
const scale = useSharedValue(1);
// This function runs on the UI thread
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [{ scale: scale.value }]
};
});
function handlePress() {
// Animation happens entirely on the UI thread
scale.value = withSpring(scale.value === 1 ? 1.2 : 1, {
damping: 10,
stiffness: 100
});
}
return (
<Pressable onPress={handlePress}>
<Animated.View style={[styles.card, animatedStyle]}>
<Text>Press me to animate</Text>
</Animated.View>
</Pressable>
);
}
Animation Composition and Interpolation
Reanimated 3 provides powerful tools for combining animations and creating complex interpolations. The composable API allows for sophisticated effects without sacrificing performance.
The interpolate
, withSequence
, withDelay
, and withRepeat
functions enable complex animations with minimal code.
import Animated, {
useSharedValue,
useAnimatedStyle,
withSequence,
withTiming,
withRepeat,
interpolate,
Extrapolation
} from 'react-native-reanimated';
function PulsingButton() {
const animation = useSharedValue(0);
// Start complex animation sequence
React.useEffect(() => {
animation.value = withRepeat(
withSequence(
withTiming(1, { duration: 500 }),
withTiming(0, { duration: 500 })
),
-1, // Infinite repetitions
true // Reverse
);
}, []);
const animatedStyle = useAnimatedStyle(() => {
const scale = interpolate(
animation.value,
[0, 1],
[1, 1.2],
Extrapolation.CLAMP
);
const opacity = interpolate(
animation.value,
[0, 0.5, 1],
[0.8, 1, 0.8],
Extrapolation.CLAMP
);
return {
transform: [{ scale }],
opacity
};
});
return (
<Animated.View style={[styles.button, animatedStyle]}>
<Text style={styles.buttonText}>Press Me</Text>
</Animated.View>
);
}
Gesture Integration
One of the most powerful features of Reanimated 3 is its deep integration with React Native Gesture Handler. This combination enables direct manipulation interfaces where elements follow the user's finger without any perceptible lag.
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, { useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated';
function DraggableCard() {
const translateX = useSharedValue(0);
const translateY = useSharedValue(0);
const context = useSharedValue({ x: 0, y: 0 });
const gesture = Gesture.Pan()
.onStart(() => {
context.value = { x: translateX.value, y: translateY.value };
})
.onUpdate((event) => {
translateX.value = context.value.x + event.translationX;
translateY.value = context.value.y + event.translationY;
})
.onEnd(() => {
translateX.value = withSpring(0);
translateY.value = withSpring(0);
});
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [
{ translateX: translateX.value },
{ translateY: translateY.value }
],
};
});
return (
<GestureDetector gesture={gesture}>
<Animated.View style={[styles.card, animatedStyle]}>
<Text>Drag and release me</Text>
</Animated.View>
</GestureDetector>
);
}
2. React Native Skia for High-Performance Rendering
Skia is a 2D graphics library used by Google Chrome and Android that provides low-level drawing capabilities. React Native Skia brings this power to React Native, enabling highly optimized rendering directly on the GPU.
For complex animations involving many elements or custom drawing, Skia offers better performance than View-based animations, as it can batch multiple drawing operations into a single GPU call.
Canvas-Based Animations
Skia excels at creating canvas-based animations with many elements, where traditional View-based approaches would struggle to maintain performance.
import { Canvas, Circle, useSharedValueEffect, useValue } from '@shopify/react-native-skia';
import { useSharedValue, withRepeat, withTiming } from 'react-native-reanimated';
function ParticleAnimation() {
// Create 50 particles with random positions
const particles = React.useMemo(() =>
Array.from({ length: 50 }, () => ({
x: Math.random() * 300,
y: Math.random() * 500,
radius: 5 + Math.random() * 10
})), []);
// Reanimated shared value for animation
const progress = useSharedValue(0);
// Skia values for each particle
const skValues = particles.map(() => useValue(0));
// Connect Reanimated to Skia
useSharedValueEffect(() => {
particles.forEach((_, i) => {
skValues[i].current = progress.value;
});
}, progress);
React.useEffect(() => {
progress.value = withRepeat(
withTiming(1, { duration: 3000 }),
-1,
true
);
}, []);
return (
<Canvas style={{ width: 300, height: 500 }}>
{particles.map((particle, i) => (
<Circle
key={i}
cx={particle.x}
cy={particle.y * skValues[i].current} // Animate y position
r={particle.radius}
color="rgba(0, 100, 255, 0.8)"
/>
))}
</Canvas>
);
}
Filter Effects and Gradients
Skia enables advanced graphical effects like blur filters, complex gradients, and masks that would be difficult or impossible with standard React Native views.
import {
Canvas,
RoundedRect,
LinearGradient,
vec,
BlurMask,
useValue
} from '@shopify/react-native-skia';
import { useSharedValue, withTiming, withRepeat } from 'react-native-reanimated';
function GradientBlurCard() {
const blurValue = useSharedValue(0);
const skBlur = useValue(0);
// Connect Reanimated to Skia
useSharedValueEffect(() => {
skBlur.current = blurValue.value * 10; // Scale up for effect
}, blurValue);
React.useEffect(() => {
blurValue.value = withRepeat(
withTiming(1, { duration: 2000 }),
-1,
true
);
}, []);
return (
<Canvas style={{ width: 300, height: 200 }}>
<RoundedRect
x={10}
y={10}
width={280}
height={180}
r={20}
>
<LinearGradient
start={vec(0, 0)}
end={vec(300, 200)}
colors={['#4C669F', '#3B5998', '#192F6A']}
/>
<BlurMask blur={skBlur} style="normal" />
</RoundedRect>
</Canvas>
);
}
3. Lottie Animations for Complex Motion Design
For complex animations designed by motion artists, Lottie provides a bridge between design tools like After Effects and your React Native application. Recent versions of Lottie have been optimized for performance, with improved caching and rendering strategies.
Optimizing Lottie Performance
While Lottie is powerful, it's important to optimize animations to maintain good performance, especially on lower-end devices.
import LottieView from 'lottie-react-native';
import { useRef } from 'react';
function OptimizedLottieAnimation() {
const animationRef = useRef<LottieView>(null);
return (
<LottieView
ref={animationRef}
source={require('./assets/animation.json')}
// Performance optimizations
cacheStrategy="strong" // Strong caching for better performance
renderMode="HARDWARE" // Use GPU acceleration
autoPlay={false} // Control playback manually for better control
loop={false}
style={{ width: 200, height: 200 }}
onLayout={() => {
// Start animation only when component is rendered
animationRef.current?.play();
}}
/>
);
}
Dynamic Properties and Interactivity
Modern Lottie implementations allow for dynamic control over animation properties, enabling interactive animations that respond to user input.
import LottieView from 'lottie-react-native';
import { useRef, useState } from 'react';
import { View, Slider, Text } from 'react-native';
function InteractiveLottie() {
const animation = useRef<LottieView>(null);
const [speed, setSpeed] = useState(1);
const [progress, setProgress] = useState(0);
return (
<View style={styles.container}>
<LottieView
ref={animation}
source={require('./assets/progress_bar.json')}
progress={progress} // Controlled progress
speed={speed} // Controlled speed
style={{ width: 300, height: 300 }}
/>
<Text>Animation Progress</Text>
<Slider
style={{ width: 200, height: 40 }}
minimumValue={0}
maximumValue={1}
value={progress}
onValueChange={setProgress}
/>
<Text>Animation Speed</Text>
<Slider
style={{ width: 200, height: 40 }}
minimumValue={0.1}
maximumValue={3}
value={speed}
onValueChange={setSpeed}
/>
</View>
);
}
4. Advanced Gesture-Driven Interfaces
Modern mobile applications often feature gesture-driven interfaces where UI elements respond directly to touch input. React Native Gesture Handler combined with Reanimated provides the tools needed for creating these experiences.
Multi-Gesture System
Complex interfaces often require multiple gestures that work together or compete for activation. The modern gesture system provides tools to compose, prioritize, and synchronize gestures.
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
useAnimatedStyle,
useSharedValue,
withSpring,
withTiming
} from 'react-native-reanimated';
function ZoomableRotatableImage() {
const scale = useSharedValue(1);
const rotation = useSharedValue(0);
const positionX = useSharedValue(0);
const positionY = useSharedValue(0);
// Create individual gestures
const panGesture = Gesture.Pan()
.onUpdate((event) => {
positionX.value = event.translationX;
positionY.value = event.translationY;
})
.onEnd(() => {
positionX.value = withSpring(0);
positionY.value = withSpring(0);
});
const pinchGesture = Gesture.Pinch()
.onUpdate((event) => {
scale.value = Math.max(0.5, Math.min(2, event.scale));
})
.onEnd(() => {
scale.value = withTiming(1, { duration: 300 });
});
const rotationGesture = Gesture.Rotation()
.onUpdate((event) => {
rotation.value = event.rotation;
})
.onEnd(() => {
rotation.value = withTiming(0, { duration: 300 });
});
// Compose gestures together
const composedGestures = Gesture.Simultaneous(
panGesture,
Gesture.Simultaneous(pinchGesture, rotationGesture)
);
const imageStyle = useAnimatedStyle(() => {
return {
transform: [
{ translateX: positionX.value },
{ translateY: positionY.value },
{ scale: scale.value },
{ rotateZ: `${rotation.value}rad` }
]
};
});
return (
<GestureDetector gesture={composedGestures}>
<Animated.Image
source={require('./assets/image.jpg')}
style={[styles.image, imageStyle]}
/>
</GestureDetector>
);
}
Velocity-Based Animations
Incorporating gesture velocity into animations creates a more natural feel, as if UI elements have physical properties like mass and friction.
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
useAnimatedStyle,
useSharedValue,
withDecay,
} from 'react-native-reanimated';
function MomentumScroller() {
const translateY = useSharedValue(0);
const context = useSharedValue(0);
const gesture = Gesture.Pan()
.onStart(() => {
context.value = translateY.value;
})
.onUpdate((event) => {
translateY.value = context.value + event.translationY;
})
.onEnd((event) => {
// Apply decay animation with velocity from gesture
translateY.value = withDecay({
velocity: event.velocityY,
clamp: [-300, 0], // Optional: constrain motion
rubberBandEffect: true // Bounce at edges
});
});
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [{ translateY: translateY.value }]
};
});
return (
<GestureDetector gesture={gesture}>
<Animated.View style={[styles.container, animatedStyle]}>
{/* Content */}
</Animated.View>
</GestureDetector>
);
}
5. Animation Performance Optimization
Even with the powerful tools available, maintaining 60+ FPS animations requires attention to optimization. Here are key strategies to ensure smooth animations on all devices.
Measuring and Monitoring Frame Rate
Understanding your app's performance is the first step to optimization. Modern tools allow for precise frame rate monitoring.
import { FrameCallback, useFrameCallback } from 'react-native-reanimated';
function AnimationPerformanceMonitor() {
const [fps, setFps] = useState(0);
const frameTimestamps = useRef<number[]>([]);
const frameCallback: FrameCallback = useCallback((timestamp) => {
// Keep last 60 frame timestamps
const timestamps = frameTimestamps.current;
timestamps.push(timestamp);
// Remove old timestamps (older than 1 second)
const oneSecondAgo = timestamp - 1000;
while (timestamps.length > 0 && timestamps[0] < oneSecondAgo) {
timestamps.shift();
}
// Calculate current FPS
const currentFps = timestamps.length;
setFps(currentFps);
}, []);
useFrameCallback(frameCallback);
return (
<View style={styles.monitor}>
<Text style={[
styles.fpsText,
{ color: fps > 55 ? 'green' : fps > 45 ? 'orange' : 'red' }
]}>
{fps} FPS
</Text>
</View>
);
}
Optimizing Layout During Animation
Layout calculations can be expensive. For smooth animations, it's important to minimize layout work during animation frames.
import { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
function OptimizedLayoutAnimation() {
const height = useSharedValue(100);
// ❌ Bad: Animating dimensions directly causes layout recalculation
const badAnimatedStyle = useAnimatedStyle(() => {
return {
height: height.value
};
});
// ✅ Good: Using transform scale avoids layout recalculation
const goodAnimatedStyle = useAnimatedStyle(() => {
const scaleY = height.value / 100;
return {
height: 100, // Fixed base height
transform: [{ scaleY }]
};
});
const toggle = () => {
height.value = withTiming(height.value === 100 ? 300 : 100);
};
return (
<View>
<Button title="Toggle Height" onPress={toggle} />
<Text>Optimized Animation:</Text>
<Animated.View style={[styles.box, goodAnimatedStyle]} />
</View>
);
}
Off-Thread Shadow Rendering
Shadows can significantly impact animation performance, especially when combined with transforms. Modern techniques allow shadows to be rendered in a more efficient way.
import { Canvas, RoundedRect, Shadow, useValue } from '@shopify/react-native-skia';
import { useSharedValue, withRepeat, withTiming } from 'react-native-reanimated';
function PerformantShadowCard() {
const shadowOffsetY = useSharedValue(0);
const skValue = useValue(0);
useSharedValueEffect(() => {
skValue.current = shadowOffsetY.value * 10;
}, shadowOffsetY);
React.useEffect(() => {
shadowOffsetY.value = withRepeat(
withTiming(1, { duration: 1000 }),
-1,
true
);
}, []);
return (
<Canvas style={{ width: 200, height: 200 }}>
<RoundedRect x={50} y={50} width={100} height={100} r={10}>
<Shadow dx={0} dy={skValue} blur={10} color="rgba(0, 0, 0, 0.3)" />
</RoundedRect>
</Canvas>
);
}
Conclusion
The React Native animation ecosystem in 2025 has never been more powerful. With tools like Reanimated 3, React Native Skia, and improved gesture handling, developers can create animations that rival or even exceed what's possible in fully native applications.
By running animations on the native thread, leveraging the GPU for complex rendering, and following performance best practices, your React Native app can deliver silky-smooth 60+ FPS animations that delight users and enhance the overall experience.
As you implement these techniques in your projects, remember that great animations should enhance usability, not distract from it. Focus on animations that provide meaningful feedback, guide users through workflows, or add personality to your brand.
🚀 Ready to catch UI issues before your users do?
Use Viewlytics to automatically capture and analyze your app's UI across real devices. Our AI-powered platform helps you identify visual bugs, layout issues, and inconsistencies before they impact user experience.
Start UI Testing with Viewlytics