React Native Advanced: Performance, Native Code and Releasing
Animations and Gestures
In this lesson you will
- Animate opacity and position with the built-in Animated API and the native driver
- Use Reanimated shared values, animated styles, withTiming and withSpring
- Build a draggable card with a pan gesture that springs back
Introduction
Good motion explains what is happening: a card slides in from where you tapped, a list item shrinks away when deleted, a sheet follows your finger. Bad motion stutters. As the last lesson showed, smoothness depends on which thread does the work. This lesson covers three tools, from the simplest to the most powerful.
Option 1: the built-in Animated API
React Native ships with Animated. You make an Animated.Value, change it over time with Animated.timing, and draw it with an Animated.View.
React (TSX)
import { useEffect, useRef } from "react";
import { Animated, StyleSheet, Text } from "react-native";
export default function FadeIn() {
const opacity = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(opacity, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}).start();
}, [opacity]);
return (
<Animated.View style={[styles.box, { opacity }]}>
<Text style={styles.text}>Hello</Text>
</Animated.View>
);
}
const styles = StyleSheet.create({
box: { padding: 24, backgroundColor: "#5b4bf5", borderRadius: 12 },
text: { color: "white", fontSize: 20 },
});The most important setting is useNativeDriver: true. The docs explain why: the Animated API "currently calculates each keyframe on-demand on the JavaScript thread unless you set useNativeDriver: true." With it, the whole animation is described to the native side up front and runs without waiting for JavaScript, so it stays smooth even when JavaScript is busy. The catch: the native driver can only animate certain properties, mainly opacity and transform. It cannot animate things like width or backgroundColor.
Quick quiz
Why set useNativeDriver: true on an Animated animation?
Option 2: Reanimated
Animated is fine for simple effects. When an animation depends on gestures or needs to react instantly, most apps use Reanimated, a library from Software Mansion. Its docs describe it as a way to "create smooth animations and interactions that run on the UI thread." Version 4 works only with the New Architecture, which is the default in new projects, and it needs the companion package react-native-worklets. Install them with Expo:
Terminal
npx expo install react-native-reanimated react-native-workletsThe key idea is the shared value: a value that lives on both the JavaScript side and the UI side, so the UI can change it without waiting for JavaScript.
React (TSX)
import { Pressable, StyleSheet, Text } from "react-native";
import Animated, { useAnimatedStyle, useSharedValue, withSpring, withTiming } from "react-native-reanimated";
export default function Bouncy() {
const scale = useSharedValue(1);
const opacity = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
opacity: opacity.value,
}));
return (
<Pressable
onPressIn={() => {
scale.value = withSpring(0.92);
opacity.value = withTiming(0.7, { duration: 120 });
}}
onPressOut={() => {
scale.value = withSpring(1);
opacity.value = withTiming(1, { duration: 120 });
}}
>
<Animated.View style={[styles.button, animatedStyle]}>
<Text style={styles.label}>Press me</Text>
</Animated.View>
</Pressable>
);
}
const styles = StyleSheet.create({
button: { backgroundColor: "#5b4bf5", paddingVertical: 14, paddingHorizontal: 28, borderRadius: 12 },
label: { color: "white", fontWeight: "bold", fontSize: 16 },
});Walk through it:
useSharedValue(1)makes a shared value starting at 1. You read and write it through.value.useAnimatedStylereturns a style that follows the shared values used inside it. When a shared value changes, the style updates on the UI thread, with no React re-render.withTiming(target, { duration })moves to the target over a set time.withSpring(target)moves with a natural spring that overshoots slightly.- You render with
Animated.Viewfrom Reanimated, not the plainView, so it can use the animated style.
Assigning an animation to .value, as in scale.value = withSpring(0.92), is how you start it.
Gestures: following the finger
Taps are easy. Dragging, swiping and pinching need a gesture system. React Native Gesture Handler recognises gestures on the native side, and it pairs naturally with Reanimated. The API below is the hook-style one in Gesture Handler 3. Wrap your screen in GestureHandlerRootView, then attach a gesture to a view with GestureDetector.
React (TSX)
import { StyleSheet, Text } from "react-native";
import { GestureDetector, GestureHandlerRootView, usePanGesture } from "react-native-gesture-handler";
import Animated, { useAnimatedStyle, useSharedValue, withSpring } from "react-native-reanimated";
export default function DragCard() {
const x = useSharedValue(0);
const y = useSharedValue(0);
const pan = usePanGesture({
onUpdate: (event) => {
x.value = event.translationX;
y.value = event.translationY;
},
onDeactivate: () => {
x.value = withSpring(0);
y.value = withSpring(0);
},
});
const style = useAnimatedStyle(() => ({
transform: [{ translateX: x.value }, { translateY: y.value }],
}));
return (
<GestureHandlerRootView style={styles.root}>
<GestureDetector gesture={pan}>
<Animated.View style={[styles.card, style]}>
<Text style={styles.label}>Drag me</Text>
</Animated.View>
</GestureDetector>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
root: { flex: 1, alignItems: "center", justifyContent: "center" },
card: { width: 160, height: 160, borderRadius: 16, backgroundColor: "#16a34a", alignItems: "center", justifyContent: "center" },
label: { color: "white", fontWeight: "bold" },
});How it works:
usePanGesturecreates a drag gesture.onUpdateruns again and again while the finger moves, andevent.translationXandtranslationYsay how far the finger has moved since it started.- Each update writes straight into the shared values, so the card follows the finger on the UI thread, with no React re-render.
onDeactivateruns when the finger lifts. Springing the values back to 0 makes the card snap home.
Because Gesture Handler and Reanimated work together, the dragging stays smooth even if your JavaScript is busy. Gesture Handler 3 has similar hooks for taps, long presses, pinch and rotation, and ways to combine them. Older tutorials show a chained style such as Gesture.Pan(), which still works in version 3 but is the older way of writing the same thing.
Quick quiz
In the drag example, what makes the card follow the finger without re-rendering React?
Choosing a tool
| Need | Use |
|---|---|
| A simple fade or slide of one element | Animated with the native driver |
| A one-off layout change, such as an item appearing | LayoutAnimation for fire-and-forget changes |
| Motion that reacts to gestures or to scroll position | Reanimated and Gesture Handler |
LayoutAnimation asks the phone to animate the next layout change automatically. The React Native docs note it is "unaffected by JS thread and main thread frame drops," but it only suits simple one-way animations that need no interruption.
Good motion habits
- Keep it short. Most interface animations feel best between 150 and 400 milliseconds.
- Animate
transformandopacity, notwidthandheight, which force the layout to be worked out again on every frame. - Respect people who prefer less motion. Both phones have a "reduce motion" setting. Reanimated provides a way to read it, so tone down large movements when it is on.
Try it yourself
Make a "swipe to delete" row: a Pan gesture that moves the row left, and when released past a threshold it animates off screen with withTiming, otherwise it springs back. Try it with and without the animations driven from shared values, and compare how smooth they feel.
Summary
AnimatedwithuseNativeDriver: trueis the simple way to animate opacity and transforms smoothly.- Reanimated shared values and animated styles run on the UI thread, and
withTimingandwithSpringstart the motion. - Gesture Handler recognises drags and taps natively, and writing gesture updates into shared values makes interfaces that follow the finger smoothly.
Lesson complete
+20 XP
Nice work. Keep going while it is fresh.