-
Choose an animation approach. Each fits different use cases:
| Approach | Framework | Best for | Performance |
|---|
| Reanimated 3 | React Native | Gesture-driven, layout, springs | UI thread (60fps) |
| Lottie | Both | Designer-created vector animations | Good |
| Rive | Both | Interactive state-machine animations | Good |
| Animated API | React Native | Simple fade/slide | JS thread |
| Implicit animations | Flutter | Simple property changes | Good |
| AnimationController | Flutter | Complex sequenced animations | Good |
Use Reanimated for anything interactive or performance-critical in RN. Use the built-in Animated API only for simple opacity/translate animations.
-
Set up Reanimated (React Native). Install:
npx expo install react-native-reanimated
Add the Babel plugin in babel.config.js:
module.exports = function (api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
plugins: ["react-native-reanimated/plugin"],
};
};
-
Create a Reanimated animation. Fade-in on mount:
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
} from "react-native-reanimated";
import { useEffect } from "react";
export function FadeIn({ children }: { children: React.ReactNode }) {
const opacity = useSharedValue(0);
useEffect(() => {
opacity.value = withTiming(1, { duration: 500 });
}, []);
const style = useAnimatedStyle(() => ({
opacity: opacity.value,
}));
return <Animated.View style={style}>{children}</Animated.View>;
}
-
Add gesture-driven animation. Swipe to dismiss with Reanimated + Gesture Handler:
npx expo install react-native-gesture-handler
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
runOnJS,
} from "react-native-reanimated";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
export function SwipeToDismiss({
children,
onDismiss,
}: {
children: React.ReactNode;
onDismiss: () => void;
}) {
const translateX = useSharedValue(0);
const gesture = Gesture.Pan()
.onUpdate((e) => {
translateX.value = e.translationX;
})
.onEnd((e) => {
if (Math.abs(e.translationX) > 150) {
runOnJS(onDismiss)();
} else {
translateX.value = withSpring(0);
}
});
const style = useAnimatedStyle(() => ({
transform: [{ translateX: translateX.value }],
}));
return (
<GestureDetector gesture={gesture}>
<Animated.View style={style}>{children}</Animated.View>
</GestureDetector>
);
}
-
Add Lottie animations. Install and use a JSON animation file:
npx expo install lottie-react-native
import LottieView from "lottie-react-native";
export function LoadingAnimation() {
return (
<LottieView
source={require("../assets/loading.json")}
autoPlay
loop
style={{ width: 200, height: 200 }}
/>
);
}
Download free animations from LottieFiles.
-
Flutter implicit animations. Animate property changes automatically:
AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: isExpanded ? 200 : 100,
height: isExpanded ? 200 : 100,
color: isActive ? Colors.blue : Colors.grey,
child: child,
)
-
Flutter explicit animations. Full control with AnimationController:
class FadeInWidget extends StatefulWidget {
final Widget child;
const FadeInWidget({super.key, required this.child});
@override
State<FadeInWidget> createState() => _FadeInWidgetState();
}
class _FadeInWidgetState extends State<FadeInWidget>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<double> _opacity;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_opacity = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeIn),
);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return FadeTransition(opacity: _opacity, child: widget.child);
}
}
-
Hero transitions (Flutter). Shared element transitions between screens:
// Source screen
Hero(tag: 'product-${product.id}', child: Image.network(product.imageUrl))
// Destination screen
Hero(tag: 'product-${product.id}', child: Image.network(product.imageUrl))