Going Pro: Job-Ready React NativePro· 50 min read

Animations & Gestures (Reanimated + Gesture Handler)

Polished apps feel alive — smooth 60fps motion and swipe/drag interactions come from Reanimated and Gesture Handler.

What you will learn

  • Animate a value smoothly with Reanimated
  • Understand why animations run on the UI thread
  • Handle a drag gesture with Gesture Handler

Why apps need motion

The difference between a rough app and a polished one is often motion: a card that slides in, a button that springs when tapped, a sheet you can drag up. Smooth motion means 60fps — the screen updates 60 times a second so nothing stutters. Two libraries deliver this in React Native, and modern courses dedicate whole projects to them: Reanimated (for animations) and Gesture Handler (for swipe, drag and pinch).

There is a built-in Animated API too, but it can stutter because it runs animation logic on the same JavaScript thread that handles your app’s work (a "thread" is just a lane the phone runs code in). If that lane is busy, the animation hiccups. Reanimated runs the animation on the separate UI thread instead — the lane that draws the screen — so it stays smooth even when JavaScript is busy. That is why it is the modern default.

Install both (one-time):

Install Reanimated and Gesture Handler
npx expo install react-native-reanimated react-native-gesture-handler

Note: Output: (Adds both packages. In a bare project Reanimated needs a small Babel config line, but Expo sets this up for you.)

A shared value — the heart of Reanimated

Reanimated’s key idea is a shared value: a special number that can be read and changed on the UI thread, so animating it never touches the slow JavaScript lane. You create one with useSharedValue, then connect it to a component’s style with useAnimatedStyle. Here a box fades and grows when you tap:

useSharedValue + useAnimatedStyle animate a box smoothly
import Animated, {
  useSharedValue, useAnimatedStyle, withTiming,
} from 'react-native-reanimated';
import { Button, View } from 'react-native';

export default function App() {
  const scale = useSharedValue(1);                 // a UI-thread number

  const boxStyle = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }],           // style follows the value
  }));

  return (
    <View>
      <Animated.View style={[{ width: 100, height: 100, backgroundColor: 'tomato' }, boxStyle]} />
      <Button title="Grow" onPress={() => { scale.value = withTiming(2); }} />
    </View>
  );
}

Note: Output: A red box. Tap "Grow" and it smoothly doubles in size over a fraction of a second (not an instant jump). withTiming(2) means "animate the value to 2 over time" instead of setting it instantly. Because scale is a shared value, this runs on the UI thread and stays at 60fps.

The moving parts, in plain terms:

  • useSharedValue(1) creates an animatable number starting at 1; you read and write it as scale.value.
  • useAnimatedStyle(() => ...) builds a style that re-computes whenever the shared value changes — here the box’s scale.
  • Animated.View is the special version of View that can use an animated style.
  • withTiming(2) is an animation helper: it eases the value to 2 smoothly instead of snapping. withSpring gives a bouncy feel.

Handling a drag gesture

Gesture Handler lets you respond to touch beyond a simple tap: drag (pan), swipe, pinch, long-press. You wrap your whole app once in a GestureHandlerRootView, define a gesture with the Gesture builder, and attach it with GestureDetector. Here a box follows your finger as you drag it:

Gesture.Pan moves a box by following the finger
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
import Animated, { useSharedValue, useAnimatedStyle } from 'react-native-reanimated';

function DraggableBox() {
  const x = useSharedValue(0);
  const y = useSharedValue(0);

  const drag = Gesture.Pan().onChange((e) => {
    x.value += e.changeX;     // move by how far the finger moved
    y.value += e.changeY;
  });

  const style = useAnimatedStyle(() => ({
    transform: [{ translateX: x.value }, { translateY: y.value }],
  }));

  return (
    <GestureDetector gesture={drag}>
      <Animated.View style={[{ width: 80, height: 80, backgroundColor: 'royalblue' }, style]} />
    </GestureDetector>
  );
}

Note: Output: A blue box you can drag anywhere on the screen with your finger; it follows smoothly and stays where you let go. Gesture.Pan() tracks a finger drag; onChange fires as it moves, giving changeX/changeY (how far since the last update), which we add to the shared values driving the box’s position.

How a gesture-driven animation fits together, step by step

Here is the full path from finger to pixels for the draggable box, in order:

  1. You wrap the app once in <GestureHandlerRootView style={{ flex: 1 }}> so gestures work anywhere inside it.
  2. You create shared values x and y for the box’s position — UI-thread numbers, so updating them never stutters.
  3. You build a gesture with Gesture.Pan() and describe what to do on each move in onChange.
  4. You attach it by wrapping the box in <GestureDetector gesture={drag}>.
  5. As the user drags, onChange runs on the UI thread and adds the finger’s movement to x and y.
  6. Because useAnimatedStyle watches those shared values, the box’s translateX/translateY update instantly — so it tracks the finger at 60fps without involving the slow JavaScript lane.

Tip: Start with the ready-made helpers before hand-rolling animations: withTiming (steady) and withSpring (bouncy) cover most needs, and Gesture.Pan / Gesture.Tap / Gesture.Pinch cover most interactions. Reach for custom math only when those are not enough.

Watch out: Inside useAnimatedStyle and gesture callbacks you are on the UI thread, where normal React state setters do not belong. Drive motion with shared values (scale.value = ...), not useState — using setState for per-frame animation defeats the whole point and stutters.

Q. Why does Reanimated stay smooth when the built-in Animated API can stutter?

Answer: Reanimated drives animations with shared values on the UI thread (the lane that draws the screen), so motion stays at 60fps even when the JavaScript thread is busy.

✍️ Practice

  1. Animate a box’s opacity from 0 to 1 with withTiming when a button is tapped.
  2. Make a Gesture.Tap() that bounces a box with withSpring on each tap.

🏠 Homework

  1. Build a draggable card with Gesture Handler that, when dragged far enough sideways, animates off-screen with withTiming (a simple swipe-to-dismiss).
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →