Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Animations with Reanimated in React Native

Animations with Reanimated

~17 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

React Native's built-in Animated API (see "React Native Reference", the "Fade in/out Animation" topic) already runs performantly on the NATIVE thread thanks to useNativeDriver: true – BUT only for a limited set of properties (opacity, transform). Reanimated takes a decisive step further: JavaScript code runs DIRECTLY on the UI thread, thanks to JSI from the last chapter.

Worklets: the core of Reanimated

A worklet is a small JavaScript function that Reanimated (thanks to JSI) "copies" to the UI thread and EXECUTES there – not on the regular JS thread. That means: even if the JS thread is blocked by expensive work (remember the artificially blocked search from "React for Professionals" chapter 33), Reanimated animations keep running UNAFFECTED, without dropping a single frame.

Installation

npx expo install react-native-reanimated
babel.config.js
module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: ['react-native-reanimated/plugin'], // MUST be the last plugin in the list
  };
};

Achtung: The Babel plugin is MANDATORY, not optional – it transforms functions marked with 'worklet' (or implicitly recognized as worklets, e.g. inside useAnimatedStyle) so they become executable on the UI thread. Without this plugin, Reanimated fails at runtime.

In practice: animating the favorite heart in ProductCard

We'll extend ProductCard's heart button from chapter 3 with a small "pop" animation on favoriting – a typical small polish effect that makes an app feel higher-quality:

components/ProductCard.js
import { memo } from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { Image } from 'expo-image';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
} from 'react-native-reanimated';

const BLURHASH = '|rF?hV%2WCj[ayj[a|j[az_NaeWBj@ayfRayfQfQM{M|azj[azf6fQfQfQIpWXofj[ayj[j[fQayWCoeoeaya}j[ayfQa{oLj?j[WVj[ayayj[fQoff7azayj[ayj[j[ayofayayayj[fQj[ayayj[ayfjj[j[ayjuayj[';

function ProductCard({ name, price, imageUrl, onPress, isFavorite, onToggleFavorite }) {
  const scale = useSharedValue(1);

  const heartStyle = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }],
  }));

  function handleToggleFavorite() {
    scale.value = withSpring(1.4, { damping: 4 }, () => {
      scale.value = withSpring(1);
    });
    onToggleFavorite();
  }

  return (
    <TouchableOpacity style={styles.card} onPress={onPress}>
      <Image
        source={imageUrl}
        style={styles.image}
        placeholder={{ blurhash: BLURHASH }}
        contentFit="cover"
        transition={200}
        cachePolicy="memory-disk"
      />
      <View style={styles.info}>
        <Text style={styles.name}>{name}</Text>
        <Text style={styles.price}>${price.toFixed(2)}</Text>
      </View>
      <TouchableOpacity style={styles.favoriteButton} onPress={handleToggleFavorite}>
        <Animated.Text style={[styles.favoriteIcon, heartStyle]}>
          {isFavorite ? '♥' : '♡'}
        </Animated.Text>
      </TouchableOpacity>
    </TouchableOpacity>
  );
}

const styles = StyleSheet.create({
  card: {
    flexDirection: 'row',
    padding: 12,
    marginBottom: 8,
    backgroundColor: '#f9fafb',
    borderRadius: 8,
    alignItems: 'center',
  },
  image: { width: 60, height: 60, borderRadius: 4 },
  info: { marginLeft: 12, flex: 1 },
  name: { fontSize: 16, fontWeight: '600' },
  price: { fontSize: 14, color: '#6b7280' },
  favoriteButton: { padding: 8 },
  favoriteIcon: { fontSize: 20, color: '#ef4444' },
});

export default memo(ProductCard);

The three core building blocks in detail

  • useSharedValue(1) – Reanimated's equivalent of useState, BUT the value lives OUTSIDE the normal React render cycle, directly accessible from the UI thread. Read/write via .value, NOT like a regular state setter.
  • useAnimatedStyle(() => ({{ ... }})) – a worklet that computes a style object from one or more shared values. Re-runs AUTOMATICALLY on the UI thread on EVERY change to scale.value, WITHOUT React itself triggering a component re-render.
  • withSpring(1.4, {{ damping: 4 }}, callback) – a spring-physics-based animation function (alternative: withTiming for linear/curve-based timing like React Native's Animated.timing) – the optional third parameter is a callback that runs after the animation COMPLETES, used here to immediately spring back to scale 1.

Achtung: <Animated.Text> instead of a plain <Text> is MANDATORY – EXACTLY like with React Native's built-in Animated API (see "React Native Reference"), only Animated.* components can accept animated style values.

Why this is faster than React Native's built-in Animated

React Native's Animated API with useNativeDriver: true does ALSO run on the native thread, but it MUST hand the animation over to the native side BEFOREHAND as a declarative configuration ("animate from X to Y over Z milliseconds") – complex, dynamic logic (e.g. "react in real time to a gesture's position", see the next chapter) can barely be expressed that way. Reanimated worklets are GENUINE JavaScript code running on the UI thread – arbitrarily complex logic, conditional branches, calculations, all with the same native performance.

Tipp: Rule of thumb: for simple, purely declarative animations (fade in/out, fixed transitions), React Native's built-in Animated is often enough – fewer dependencies, less to learn. Reanimated pays off once animations need to react to user GESTURES in real time (swipe gestures, drag-and-drop, interactive transitions) – exactly the topic of the next chapter.