AniUI Academy

Animations and the Native Driver

Animated.timing and useNativeDriver: true, why moving frame updates onto the native thread keeps them smooth even when JS is busy, and its real limits.

9 min read

React Native ships a built-in animation system, and the single most important thing to understand about it isn't the API surface — it's one option, useNativeDriver, and exactly what it does to which thread does the work.

The basic shape: Animated.Value and Animated.timing

The core building block is an Animated.Value — a number React Native knows how to animate smoothly, as distinct from an ordinary piece of useState you'd re-render on every frame yourself:

import { useRef } from "react";
import { Animated, Pressable } from "react-native";
 
function FadeInBox() {
  const opacity = useRef(new Animated.Value(0)).current;
 
  const fadeIn = () => {
    Animated.timing(opacity, {
      toValue: 1,
      duration: 300,
      useNativeDriver: true,
    }).start();
  };
 
  return (
    <Pressable onPress={fadeIn}>
      <Animated.View style={{ opacity }} />
    </Pressable>
  );
}

Animated.timing (there's also Animated.spring, for physically-based motion instead of a fixed duration curve) describes how opacity should change over time, and .start() actually kicks the animation off. Note Animated.View rather than a plain View — animating a style value requires the animated variant of the component so React Native knows which node to actually update as the value changes.

The one option that matters most: useNativeDriver

useNativeDriver: true is a small option with an outsized effect on how smooth an animation actually feels. Recall from the bridge/JSI lesson that your JS code runs on one thread, and the actual native views are rendered on a separate native UI thread. Without the native driver, every single frame of an animation is computed and applied through the normal JS-to- native communication path — meaning if the JS thread is busy doing something else (handling a large state update, running an expensive computation, responding to a network callback), the animation genuinely has to wait its turn, and that shows up as visible stutter.

With useNativeDriver: true, the animation's entire timeline is handed off to the native side once, up front — and from that point, the native UI thread runs the animation itself, frame by frame, entirely independent of whatever the JS thread is doing. A busy JS thread can no longer make this specific animation stutter, because it's no longer JS's job to drive it frame by frame at all.

// Without the native driver: every frame round-trips through JS
Animated.timing(translateX, {
  toValue: 100,
  duration: 300,
  useNativeDriver: false, // frame updates depend on the JS thread being free
}).start();
 
// With the native driver: handed to native, runs independently of JS load
Animated.timing(translateX, {
  toValue: 100,
  duration: 300,
  useNativeDriver: true,
}).start();

The real constraint: not every property qualifies

useNativeDriver: true isn't universally available for every style property, and this is a genuine, long-standing limitation rather than a bug to work around. Properties like transform (translate, scale, rotate) and opacity are generally well-supported by the native driver. Layout- affecting properties — things like width, height, or flexBasis — have historically not been drivable natively, because animating layout requires re-running layout calculations that the native driver's fast path doesn't cover the same way. Practically, this means an animation that changes width directly often can't use useNativeDriver: true at all, and reaching for a transform: scale instead, where the actual visual effect allows it, is a common, deliberate workaround.

// Often fine with the native driver — a transform, not a layout change
Animated.timing(scale, { toValue: 1.2, useNativeDriver: true, duration: 200 }).start();
 
// Historically not eligible for the native driver — width is a layout property
Animated.timing(widthValue, { toValue: 300, useNativeDriver: false, duration: 200 }).start();

If you try to set useNativeDriver: true on a property it doesn't support, React Native raises a clear error rather than silently doing the wrong thing — so this constraint tends to surface quickly during development rather than as a subtle production bug.

Beyond Animated: react-native-reanimated

For animations that need to react to gestures in real time — a card that follows your finger and then springs back, a swipe-to-dismiss interaction — the built-in Animated API can get unwieldy. react-native-reanimated is the modern, more powerful library the ecosystem has largely standardized on for that class of problem, worth knowing by name even without going deep on its API here: it's built specifically to let complex, gesture-driven animation logic run with the same "independent of a busy JS thread" guarantee that useNativeDriver provides for simpler cases.

What to remember

  • Animated.Value plus Animated.timing/Animated.spring and .start() is the built-in animation model; Animated.View (not a plain View) is required to animate a style value.
  • useNativeDriver: true hands the animation to the native UI thread, so it stays smooth even if the JS thread is busy — without it, every frame round-trips through JS and can stutter under JS-thread load.
  • Not every property can use the native driver: transform and opacity are generally well-supported, while layout properties like width and height historically are not.
  • Attempting useNativeDriver: true on an unsupported property raises a clear error rather than silently misbehaving.
  • react-native-reanimated is the ecosystem-standard, more powerful option for complex, gesture-driven animation, worth knowing by name even at a purely conceptual level.

Check yourself

4 questions · pass 3/4 to unlock The New Architecture: Fabric, TurboModules, and JSI

up to 50
  1. 1.What does setting useNativeDriver: true actually change about how an Animated.timing animation runs?

  2. 2.Without useNativeDriver, what happens to an Animated value's frame updates?

  3. 3.What's a genuine, real constraint on which style properties useNativeDriver: true can animate?

  4. 4.What is react-native-reanimated, at a conceptual level?

4 left to answer