React Native Reanimated 4: CSS Animations, Gesture, dan Layout Animation di Expo

Panduan praktis React Native Reanimated 4 di Expo SDK 55. Mulai dari CSS Animations, gesture-driven animations, layout animations, shared element transitions, sampai tips performa 60 FPS — lengkap dengan contoh kode yang bisa langsung dicoba.

Pendahuluan: Kenapa Animasi Itu Penting di Aplikasi Mobile?

Pernahkah kamu membuka sebuah aplikasi dan langsung merasa "wah, ini smooth banget"? Nah, kemungkinan besar yang bikin kesan pertama itu begitu kuat adalah animasi. Animasi bukan sekadar hiasan — ia itu semacam bahasa komunikasi antara aplikasi dan pengguna. Transisi halus saat berpindah halaman, feedback visual ketika tombol ditekan, atau efek loading yang elegan bisa jadi pembeda antara aplikasi yang terasa profesional dan yang… yah, terasa kayak tugas kuliah semester tiga.

Di ekosistem React Native, library yang sudah jadi standar industri untuk animasi adalah React Native Reanimated dari Software Mansion. Dan di tahun 2026 ini, dengan dirilisnya Reanimated versi 4, kemampuannya makin mengesankan — kamu bisa bikin animasi pakai sintaks CSS yang familiar, sambil tetap punya akses penuh ke worklets untuk skenario yang lebih kompleks.

Jujur, waktu pertama kali coba CSS Animations API-nya Reanimated 4, rasanya kayak "kok dari dulu nggak gini sih?"

Oke, dalam panduan ini kita akan bahas semua yang perlu kamu ketahui tentang React Native Reanimated 4 — dari instalasi, konsep dasar shared values, API animasi CSS yang baru, gesture-driven animations, layout animations, sampai best practices buat performa optimal. Semua contoh kode di sini bisa langsung dijalankan di project Expo SDK 55.

Apa yang Baru di Reanimated 4?

Reanimated 4 bukan sekadar update minor. Ini lompatan besar yang membawa paradigma baru dalam cara kita bikin animasi di React Native. Yuk, kita lihat perubahan-perubahan utamanya.

API Animasi CSS yang Deklaratif

Fitur paling menonjol di Reanimated 4 adalah hadirnya CSS Animations dan Transitions API. Tim Software Mansion memilih standar CSS karena sudah battle-tested dan dikenal luas oleh developer web. Artinya, kamu bisa nulis animasi dengan cara yang sama seperti di web — pakai transitionProperty, transitionDuration, animationName dengan keyframes, dan seterusnya.

Keuntungan utamanya? Kode jadi lebih ringkas (nggak perlu hook calls) dan lebih mudah dioptimasi oleh Reanimated secara internal, karena library-nya paham detail lebih banyak tentang atribut mana yang sedang dianimasi.

Worklets Dipindahkan ke Library Terpisah

Implementasi worklets sekarang ada di library terpisah bernama react-native-worklets. Pemisahan ini dilakukan demi modularitas yang lebih baik. Beberapa fungsi juga mengalami perubahan nama:

// Perubahan nama API di Reanimated 4
// Lama (Reanimated 3)        → Baru (Reanimated 4 / Worklets)
// runOnJS                     → scheduleOnRN
// runOnUI                     → scheduleOnUI
// executeOnUIRuntimeSync      → runOnUISync
// runOnRuntime                → scheduleOnRuntime
// makeShareableCloneRecursive → createSerializable

Fungsi-fungsi lama masih di-re-export dari react-native-reanimated untuk backward compatibility, tapi sudah ditandai deprecated dan bakal dihapus di rilis mendatang. Jadi mulai biasakan pakai nama baru, ya.

Perubahan pada withSpring

Parameter restDisplacementThreshold dan restSpeedThreshold sudah diganti dengan satu parameter baru: energyThreshold. Parameter duration sekarang merepresentasikan perceptual duration dari animasi, bukan waktu aktual sampai selesai.

Dalam kebanyakan kasus, cukup hapus parameter lama tanpa perlu override energyThreshold. Simpel.

Hanya Mendukung New Architecture

Ini yang penting banget: Reanimated 4.x hanya mendukung New Architecture (Fabric renderer) dan sudah sepenuhnya menghilangkan dukungan untuk Legacy Architecture (Paper). Kalau aplikasi kamu masih pakai arsitektur lama, tetaplah di Reanimated 3.x sambil mempersiapkan migrasi ke New Architecture.

Instalasi dan Setup di Expo

Memulai dengan Reanimated 4 di project Expo itu cukup straightforward. Berikut langkah-langkahnya.

Langkah 1: Install Package

# Install Reanimated dan Worklets
npx expo install react-native-reanimated react-native-worklets

# Install juga Gesture Handler (untuk animasi berbasis gesture)
npx expo install react-native-gesture-handler

Langkah 2: Konfigurasi Babel

Kabar baiknya, kalau kamu pakai Expo SDK 50 ke atas, plugin Babel untuk Worklets sudah otomatis dikonfigurasi oleh babel-preset-expo. Jadi file babel.config.js kamu bisa tetap sederhana:

// babel.config.js (Expo Managed Workflow)
module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
  };
};

Untuk project bare workflow (React Native CLI), kamu perlu menambahkan plugin secara manual:

// babel.config.js (Bare Workflow)
module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['module:metro-react-native-babel-preset'],
    plugins: [
      'react-native-worklets/plugin', // Harus di posisi terakhir!
    ],
  };
};

Penting: Jangan tambahkan react-native-reanimated/plugin dan react-native-worklets/plugin sekaligus — ini bakal menyebabkan konflik karena plugin worklets sudah termasuk di dalam plugin reanimated. Percaya deh, error-nya bikin pusing.

Langkah 3: Setup GestureHandlerRootView

Kalau kamu berencana pakai animasi berbasis gesture, bungkus root aplikasi kamu dengan GestureHandlerRootView:

// app/_layout.tsx (Expo Router)
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <Stack />
    </GestureHandlerRootView>
  );
}

Langkah 4: Bersihkan Cache Metro

Setelah mengubah konfigurasi Babel, selalu bersihkan cache Metro bundler. Langkah ini sering kelupaan dan jadi sumber kebingungan:

npx expo start --clear

Konsep Dasar: Shared Values dan Animated Styles

Sebelum masuk ke contoh-contoh animasi, penting untuk paham dua konsep fundamental di Reanimated dulu.

Shared Values

Shared value adalah mekanisme state inti di Reanimated. Anggap ini seperti useState dari React, tapi nilainya secara otomatis disinkronkan antara JavaScript thread dan UI thread. Inilah yang bikin animasi bisa berjalan mulus di 60 FPS tanpa terganggu pekerjaan berat di JavaScript thread.

import { useSharedValue } from 'react-native-reanimated';

function MyComponent() {
  // Membuat shared value dengan nilai awal 0
  const translateX = useSharedValue(0);

  // Mengakses dan mengubah nilai via .value
  const handlePress = () => {
    translateX.value = 100; // Perubahan langsung
  };

  return (/* ... */);
}

Yang membedakan shared value dari state biasa:

  • Perubahan nilai tidak menyebabkan re-render komponen React
  • Nilainya bisa diakses langsung dari worklets yang berjalan di UI thread
  • Update terjadi secara sinkron di UI thread, tanpa melewati bridge

Poin pertama itu yang sering bikin developer baru kaget — "kok nggak re-render?" Ya, memang itu tujuannya.

Animated Styles

Hook useAnimatedStyle menghubungkan shared values ke properti style komponen. Setiap kali shared value berubah, style otomatis diperbarui tanpa menyebabkan re-render React:

import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
} from 'react-native-reanimated';
import { View, Pressable, Text, StyleSheet } from 'react-native';

function AnimatedBox() {
  const scale = useSharedValue(1);

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

  const handlePressIn = () => {
    scale.value = withSpring(0.9);
  };

  const handlePressOut = () => {
    scale.value = withSpring(1);
  };

  return (
    <Pressable onPressIn={handlePressIn} onPressOut={handlePressOut}>
      <Animated.View style={[styles.box, animatedStyle]}>
        <Text style={styles.text}>Tekan saya!</Text>
      </Animated.View>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  box: {
    width: 150,
    height: 150,
    backgroundColor: '#6C63FF',
    borderRadius: 16,
    justifyContent: 'center',
    alignItems: 'center',
  },
  text: {
    color: 'white',
    fontWeight: 'bold',
  },
});

Fungsi Animasi Bawaan: withTiming, withSpring, dan withDecay

Reanimated menyediakan tiga fungsi animasi utama yang bisa kamu gunakan untuk berbagai skenario. Masing-masing punya karakter yang berbeda.

withTiming — Animasi Berbasis Waktu

Animasi yang berjalan selama durasi tertentu dengan easing function yang bisa dikustomisasi. Ini yang paling predictable dan mudah dipahami:

import { withTiming, Easing } from 'react-native-reanimated';

// Animasi sederhana selama 300ms
opacity.value = withTiming(1, { duration: 300 });

// Dengan custom easing
translateY.value = withTiming(0, {
  duration: 500,
  easing: Easing.bezier(0.25, 0.1, 0.25, 1),
});

withSpring — Animasi Berbasis Pegas

Ini favorit pribadi saya. Animasi spring menghasilkan gerakan yang terasa lebih natural dan responsif — ideal untuk interaksi pengguna. Ada alasan kenapa Apple pakai spring animations di mana-mana.

import { withSpring } from 'react-native-reanimated';

// Spring default
translateX.value = withSpring(100);

// Dengan konfigurasi kustom
translateX.value = withSpring(100, {
  damping: 15,        // Tingkat redaman (semakin tinggi, semakin cepat berhenti)
  stiffness: 150,     // Kekakuan pegas (semakin tinggi, semakin cepat)
  mass: 1,            // Massa objek
});

withDecay — Animasi Peluruhan Momentum

Sempurna untuk gesture-based interactions di mana kamu pengen elemen terus bergerak setelah pengguna melepas jarinya, lalu pelan-pelan berhenti. Kayak nge-scroll di iOS, gitu.

import { withDecay } from 'react-native-reanimated';

// Biasa dipakai di onEnd callback gesture
translateX.value = withDecay({
  velocity: event.velocityX,   // Kecepatan dari gesture
  deceleration: 0.998,         // Faktor peluruhan (0-1, semakin tinggi semakin lambat berhenti)
  clamp: [-200, 200],          // Batas minimum dan maksimum
});

Menggabungkan Animasi: withSequence, withDelay, dan withRepeat

Nah, di sinilah mulai seru. Kamu bisa menggabungkan fungsi-fungsi animasi untuk menciptakan efek yang lebih kompleks:

import {
  withSequence,
  withDelay,
  withRepeat,
  withTiming,
  withSpring,
} from 'react-native-reanimated';

// Animasi berurutan: kecilkan → besarkan
scale.value = withSequence(
  withTiming(0.8, { duration: 100 }),
  withSpring(1.2),
  withSpring(1)
);

// Animasi dengan delay 500ms
opacity.value = withDelay(500, withTiming(1, { duration: 300 }));

// Animasi berulang (infinite pulse)
scale.value = withRepeat(
  withSequence(
    withTiming(1.1, { duration: 500 }),
    withTiming(1, { duration: 500 })
  ),
  -1,    // -1 = infinite loop
  true   // reverse setiap iterasi
);

Fitur Baru: CSS Transitions di Reanimated 4

Oke, ini salah satu fitur yang paling bikin saya excited. Reanimated 4 memungkinkan kamu mendefinisikan animasi transisi menggunakan sintaks yang mirip CSS. Buat developer yang terbiasa dengan web development, ini bakal terasa sangat intuitif.

Cara Kerja CSS Transitions

Dengan CSS Transitions, kamu cukup deklarasikan properti mana yang harus dianimasi dan bagaimana caranya. Ketika state berubah, Reanimated otomatis menangani transisi dari nilai lama ke nilai baru. Gampang banget:

import Animated from 'react-native-reanimated';
import { useState } from 'react';
import { Pressable, Text, StyleSheet } from 'react-native';

function ExpandableCard() {
  const [expanded, setExpanded] = useState(false);

  return (
    <Pressable onPress={() => setExpanded(!expanded)}>
      <Animated.View
        style={[
          styles.card,
          {
            height: expanded ? 300 : 80,
            backgroundColor: expanded ? '#6C63FF' : '#E0E0E0',
            // Properti transisi CSS
            transitionProperty: 'height',
            transitionDuration: 300,
            transitionTimingFunction: 'ease-in-out',
          },
        ]}
      >
        <Text style={{ color: expanded ? 'white' : 'black' }}>
          {expanded ? 'Tutup' : 'Buka Detail'}
        </Text>
      </Animated.View>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  card: {
    borderRadius: 12,
    padding: 16,
    justifyContent: 'center',
    alignItems: 'center',
    marginVertical: 8,
  },
});

Properti yang didukung: transitionProperty, transitionDuration, transitionDelay, transitionTimingFunction, dan transitionBehavior.

Kapan Pakai CSS Transitions vs Shared Values

CSS Transitions cocok banget untuk animasi yang dipicu oleh perubahan state — toggle, expand/collapse, perubahan warna, dan semacamnya. Tapi untuk animasi yang butuh kontrol real-time (gesture, scroll, orchestrasi kompleks), shared values dan worklets masih jadi pendekatan yang direkomendasikan.

Aturan praktisnya: kalau animasinya bisa diekspresikan sebagai "dari nilai A ke nilai B saat state berubah", pakai CSS Transitions. Kalau lebih dari itu, pakai shared values.

Fitur Baru: CSS Keyframe Animations

Selain transitions, Reanimated 4 juga mendukung CSS Keyframe Animations untuk animasi multi-tahap yang lebih kompleks:

import Animated from 'react-native-reanimated';
import { StyleSheet } from 'react-native';

// Definisikan keyframes
const pulseKeyframes = {
  0: { transform: [{ scale: 1 }], opacity: 1 },
  50: { transform: [{ scale: 1.15 }], opacity: 0.7 },
  100: { transform: [{ scale: 1 }], opacity: 1 },
};

function PulsingDot() {
  return (
    <Animated.View
      style={[
        styles.dot,
        {
          animationName: pulseKeyframes,
          animationDuration: 1500,
          animationIterationCount: 'infinite',
          animationTimingFunction: 'ease-in-out',
        },
      ]}
    />
  );
}

const styles = StyleSheet.create({
  dot: {
    width: 20,
    height: 20,
    borderRadius: 10,
    backgroundColor: '#FF6B6B',
  },
});

Properti animasi CSS yang didukung: animationName (objek keyframes), animationDuration, animationDelay, animationIterationCount, animationDirection, animationFillMode, dan animationTimingFunction.

Preset Animasi CSS Siap Pakai

Yang lebih keren lagi, tim Software Mansion juga menyediakan library react-native-css-animations yang berisi preset animasi populer seperti pulse, bounce, shimmer, dan lainnya. Tinggal import dan pakai:

# Install preset animasi
npm install react-native-css-animations
import { pulse, bounce, shimmer } from 'react-native-css-animations';
import Animated from 'react-native-reanimated';

// Loading skeleton dengan efek shimmer
function SkeletonLoader() {
  return <Animated.View style={[styles.skeleton, shimmer]} />;
}

// Indikator scroll-down dengan efek bounce
function ScrollIndicator() {
  return (
    <Animated.View style={[styles.arrow, bounce]}>
      {/* Arrow icon */}
    </Animated.View>
  );
}

Animasi Berbasis Gesture

Salah satu kekuatan terbesar Reanimated adalah integrasinya dengan React Native Gesture Handler. Kombinasi ini memungkinkan kamu bikin animasi yang merespons sentuhan pengguna secara real-time di UI thread — tanpa lag sama sekali. Dan sejujurnya, ini yang bikin Reanimated susah ditandingi.

Tap Gesture: Feedback Visual saat Ditekan

import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
} from 'react-native-reanimated';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import { StyleSheet } from 'react-native';

function TapButton() {
  const scale = useSharedValue(1);

  const tapGesture = Gesture.Tap()
    .onBegin(() => {
      // Callback ini otomatis dijalankan sebagai worklet di UI thread
      scale.value = withSpring(0.92);
    })
    .onFinalize(() => {
      scale.value = withSpring(1);
    });

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

  return (
    <GestureDetector gesture={tapGesture}>
      <Animated.View style={[styles.button, animatedStyle]}>
        {/* Konten tombol */}
      </Animated.View>
    </GestureDetector>
  );
}

const styles = StyleSheet.create({
  button: {
    backgroundColor: '#6C63FF',
    paddingVertical: 16,
    paddingHorizontal: 32,
    borderRadius: 12,
    alignItems: 'center',
  },
});

Pan Gesture: Drag and Drop

Implementasi drag and drop yang smooth. Kuncinya di sini adalah menyimpan posisi sebelumnya supaya elemen nggak loncat-loncat:

import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
} from 'react-native-reanimated';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import { StyleSheet } from 'react-native';

function DraggableCard() {
  const translateX = useSharedValue(0);
  const translateY = useSharedValue(0);
  const contextX = useSharedValue(0);
  const contextY = useSharedValue(0);
  const scale = useSharedValue(1);

  const panGesture = Gesture.Pan()
    .onStart(() => {
      // Simpan posisi awal saat gesture dimulai
      contextX.value = translateX.value;
      contextY.value = translateY.value;
      scale.value = withSpring(1.05);
    })
    .onChange((event) => {
      // Update posisi berdasarkan pergerakan jari + posisi awal
      translateX.value = contextX.value + event.translationX;
      translateY.value = contextY.value + event.translationY;
    })
    .onFinalize(() => {
      scale.value = withSpring(1);
    });

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

  return (
    <GestureDetector gesture={panGesture}>
      <Animated.View style={[styles.card, animatedStyle]}>
        {/* Konten kartu */}
      </Animated.View>
    </GestureDetector>
  );
}

const styles = StyleSheet.create({
  card: {
    width: 200,
    height: 120,
    backgroundColor: '#6C63FF',
    borderRadius: 16,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 4 },
    shadowOpacity: 0.3,
    shadowRadius: 8,
    elevation: 8,
  },
});

Tanpa menyimpan konteks posisi sebelumnya (via contextX dan contextY), elemen akan melompat kembali ke posisi awal setiap kali gesture baru dimulai. Hasilnya? Pengalaman yang terasa glitchy dan nggak enak dipakai.

Swipe-to-Dismiss dengan Momentum

Contoh implementasi swipe card yang punya momentum setelah pengguna melepas jari — mirip kayak Tinder card stack:

import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
  withDecay,
  withTiming,
  interpolate,
  runOnJS,
} from 'react-native-reanimated';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import { Dimensions, StyleSheet } from 'react-native';

const { width: SCREEN_WIDTH } = Dimensions.get('window');
const SWIPE_THRESHOLD = SCREEN_WIDTH * 0.3;

function SwipeableCard({ onDismiss }) {
  const translateX = useSharedValue(0);
  const rotation = useSharedValue(0);
  const opacity = useSharedValue(1);

  const panGesture = Gesture.Pan()
    .onUpdate((event) => {
      translateX.value = event.translationX;
      rotation.value = interpolate(
        event.translationX,
        [-SCREEN_WIDTH, 0, SCREEN_WIDTH],
        [-15, 0, 15]
      );
    })
    .onEnd((event) => {
      if (Math.abs(translateX.value) > SWIPE_THRESHOLD) {
        // Swipe cukup jauh — dismiss kartu
        const direction = translateX.value > 0 ? 1 : -1;
        translateX.value = withTiming(
          direction * SCREEN_WIDTH * 1.5,
          { duration: 300 }
        );
        opacity.value = withTiming(0, { duration: 300 });
        // Panggil callback di JS thread
        runOnJS(onDismiss)();
      } else {
        // Kembali ke posisi awal
        translateX.value = withSpring(0);
        rotation.value = withSpring(0);
      }
    });

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [
      { translateX: translateX.value },
      { rotate: `${rotation.value}deg` },
    ],
    opacity: opacity.value,
  }));

  return (
    <GestureDetector gesture={panGesture}>
      <Animated.View style={[styles.swipeCard, animatedStyle]}>
        {/* Konten kartu */}
      </Animated.View>
    </GestureDetector>
  );
}

const styles = StyleSheet.create({
  swipeCard: {
    width: SCREEN_WIDTH * 0.85,
    height: 300,
    backgroundColor: 'white',
    borderRadius: 20,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.25,
    shadowRadius: 10,
    elevation: 5,
  },
});

Layout Animations: Entering, Exiting, dan Keyframes

Layout animations bikin kamu bisa menambahkan animasi secara otomatis ketika komponen ditambahkan atau dihapus dari view hierarchy. Ini sangat berguna untuk animasi daftar, notifikasi, toast messages, dan transisi UI lainnya.

Entering dan Exiting Animations

Reanimated menyediakan banyak preset animasi entering/exiting yang langsung siap pakai. Tinggal pilih mana yang cocok:

import Animated, {
  FadeIn,
  FadeOut,
  SlideInLeft,
  SlideOutRight,
  BounceIn,
  ZoomIn,
  FlipInXUp,
  LightSpeedInLeft,
} from 'react-native-reanimated';
import { View, StyleSheet } from 'react-native';

function NotificationItem({ message, visible }) {
  if (!visible) return null;

  return (
    <Animated.View
      entering={SlideInLeft.duration(400).springify().damping(15)}
      exiting={SlideOutRight.duration(300)}
      style={styles.notification}
    >
      {/* Konten notifikasi */}
    </Animated.View>
  );
}

// Staggered list — item muncul satu per satu
function AnimatedList() {
  return (
    <View>
      <Animated.View entering={FadeIn.delay(0).duration(400)}>
        {/* Item 1 */}
      </Animated.View>
      <Animated.View entering={FadeIn.delay(100).duration(400)}>
        {/* Item 2 — muncul 100ms setelah item 1 */}
      </Animated.View>
      <Animated.View entering={FadeIn.delay(200).duration(400)}>
        {/* Item 3 — muncul 200ms setelah item 1 */}
      </Animated.View>
    </View>
  );
}

Efek staggered ini kecil tapi impactful banget buat first impression pengguna.

Modifier Animasi

Setiap preset animasi bisa dikustomisasi pakai modifier:

  • .duration(ms) — Durasi animasi dalam milidetik (default: 600ms)
  • .delay(ms) — Delay sebelum animasi dimulai
  • .springify() — Mengubah animasi jadi berbasis spring
  • .damping(value) — Tingkat redaman spring
  • .stiffness(value) — Kekakuan spring
  • .randomDelay() — Delay acak (bagus untuk efek staggered yang lebih organik)
  • .easing(fn) — Custom easing function (hanya untuk time-based, nggak berlaku kalau .springify() digunakan)

Custom Layout Animation dengan Keyframe

Untuk animasi yang lebih spesifik dan nggak bisa dicapai dengan preset, gunakan Keyframe API:

import { Keyframe, Easing } from 'react-native-reanimated';

// Animasi masuk: slide dari bawah sambil fade in dan sedikit rotate
const customEntering = new Keyframe({
  0: {
    opacity: 0,
    transform: [
      { translateY: 50 },
      { rotate: '-5deg' },
    ],
  },
  60: {
    opacity: 1,
    transform: [
      { translateY: -10 },
      { rotate: '2deg' },
    ],
    easing: Easing.out(Easing.quad),
  },
  100: {
    opacity: 1,
    transform: [
      { translateY: 0 },
      { rotate: '0deg' },
    ],
  },
}).duration(500);

function AnimatedCard() {
  return (
    <Animated.View entering={customEntering} style={styles.card}>
      {/* Konten */}
    </Animated.View>
  );
}

Tips penting: Pastikan semua properti dalam array transform ditulis dalam urutan yang sama di setiap keyframe. Urutan yang berbeda bakal menghasilkan animasi yang nggak sesuai ekspektasi — dan ini bisa jadi bug yang susah dilacak.

Shared Element Transitions

Reanimated juga mendukung Shared Element Transitions — animasi halus ketika elemen yang sama berpindah antar layar. Fitur ini bekerja dengan baik bersama Expo Router dan React Navigation. Kalau kamu pernah lihat efek transisi di aplikasi Google Photos, konsepnya mirip:

import Animated from 'react-native-reanimated';
import { Link } from 'expo-router';
import { Pressable, Text, StyleSheet } from 'react-native';

// Screen 1: Daftar produk
function ProductList() {
  return (
    <Link href="/product/1" asChild>
      <Pressable>
        <Animated.Image
          sharedTransitionTag="product-image-1"
          source={{ uri: 'https://example.com/product.jpg' }}
          style={styles.thumbnail}
        />
        <Text>Nama Produk</Text>
      </Pressable>
    </Link>
  );
}

// Screen 2: Detail produk
function ProductDetail() {
  return (
    <Animated.Image
      sharedTransitionTag="product-image-1"
      source={{ uri: 'https://example.com/product.jpg' }}
      style={styles.heroImage}
    />
  );
}

Reanimated otomatis mendeteksi komponen dengan sharedTransitionTag yang sama di layar lama dan baru, lalu bikin animasi transisi yang smooth di antara keduanya. Kuncinya: pastikan tag-nya unik per elemen (misalnya pakai ID produk).

Animasi Berbasis Scroll

Hook useAnimatedScrollHandler memungkinkan kamu menangkap event scroll dan menggunakannya untuk menggerakkan animasi. Header yang menyusut saat di-scroll, parallax effect, progress indicator — semua bisa dibikin dengan hook ini:

import Animated, {
  useSharedValue,
  useAnimatedStyle,
  useAnimatedScrollHandler,
  interpolate,
  Extrapolation,
} from 'react-native-reanimated';
import { StyleSheet, Text } from 'react-native';

function CollapsibleHeader() {
  const scrollY = useSharedValue(0);

  const scrollHandler = useAnimatedScrollHandler({
    onScroll: (event) => {
      scrollY.value = event.contentOffset.y;
    },
  });

  const headerStyle = useAnimatedStyle(() => {
    const height = interpolate(
      scrollY.value,
      [0, 150],
      [200, 60],
      Extrapolation.CLAMP
    );

    const opacity = interpolate(
      scrollY.value,
      [0, 100],
      [1, 0],
      Extrapolation.CLAMP
    );

    return { height, opacity };
  });

  const titleStyle = useAnimatedStyle(() => {
    const fontSize = interpolate(
      scrollY.value,
      [0, 150],
      [28, 18],
      Extrapolation.CLAMP
    );

    return { fontSize };
  });

  return (
    <>
      <Animated.View style={[styles.header, headerStyle]}>
        <Animated.Text style={[styles.title, titleStyle]}>
          Beranda
        </Animated.Text>
      </Animated.View>
      <Animated.ScrollView onScroll={scrollHandler} scrollEventThrottle={16}>
        {/* Konten scrollable */}
      </Animated.ScrollView>
    </>
  );
}

const styles = StyleSheet.create({
  header: {
    backgroundColor: '#6C63FF',
    justifyContent: 'flex-end',
    paddingHorizontal: 16,
    paddingBottom: 12,
  },
  title: {
    color: 'white',
    fontWeight: 'bold',
  },
});

Best Practices dan Tips Performa

Supaya animasi kamu selalu berjalan mulus di 60 FPS, ada beberapa hal yang perlu diperhatikan. Dari pengalaman, kebanyakan masalah performa animasi berasal dari kesalahan-kesalahan kecil yang sebenarnya mudah dihindari.

1. Gunakan Komponen Animated, Bukan View Biasa

Ini kedengarannya obvious, tapi sering terlupakan (terutama pas copy-paste kode). Animated styles hanya bisa diterapkan pada komponen yang di-wrap oleh Reanimated: Animated.View, Animated.Text, Animated.Image, Animated.ScrollView, Animated.FlatList.

2. Hindari Membuat Layout Animation Builder di Dalam Render

Definisikan layout animation builder di luar komponen atau bungkus dengan useMemo supaya nggak dibuat ulang setiap render:

// Baik — di luar komponen
const enteringAnimation = FadeIn.duration(300).springify();

function MyComponent() {
  return <Animated.View entering={enteringAnimation} />;
}

// Buruk — di dalam render (membuat instance baru setiap render)
function MyComponent() {
  return <Animated.View entering={FadeIn.duration(300).springify()} />;
}

3. Gunakan interpolate untuk Mapping Nilai

Fungsi interpolate itu teman terbaik kamu di Reanimated. Sangat berguna untuk memetakan satu range nilai ke range lainnya — posisi scroll ke opacity, posisi gesture ke rotasi, dan sebagainya. Selalu gunakan Extrapolation.CLAMP untuk mencegah nilai keluar dari range yang diinginkan.

4. Pilih API yang Tepat untuk Skenario yang Tepat

SkenarioAPI yang Direkomendasikan
Toggle, modal, expand/collapseCSS Transitions
Animasi multi-tahap (loading, onboarding)CSS Keyframe Animations
Drag, swipe, pinch, rotateGesture Handler + Shared Values
Header collapse, parallaxuseAnimatedScrollHandler + Shared Values
Animasi antar-layarShared Element Transitions
Animasi masuk/keluar komponenLayout Animations (entering/exiting)

5. Jangan Campurkan Animasi di JS Thread dan UI Thread

Kalau kamu pakai Reanimated untuk satu properti, pastikan properti yang sama nggak diubah oleh setState di JS thread secara bersamaan. Ini bisa menyebabkan konflik dan perilaku yang nggak terduga — animasi bisa "loncat" atau flickering.

Migrasi dari Reanimated 3.x ke 4.x

Kabar baiknya, migrasi dari Reanimated 3.x ke 4.x relatif straightforward. Berikut checklist yang perlu kamu ikuti:

  1. Pastikan aplikasi sudah menggunakan New Architecture — Reanimated 4 nggak mendukung Legacy Architecture sama sekali.
  2. Install react-native-worklets sebagai dependency terpisah.
  3. Update Babel config — ganti react-native-reanimated/plugin jadi react-native-worklets/plugin (untuk bare workflow).
  4. Update nama fungsirunOnJSscheduleOnRN, runOnUIscheduleOnUI, dll. Nama lama masih berfungsi tapi deprecated.
  5. Hapus parameter restDisplacementThreshold dan restSpeedThreshold dari konfigurasi withSpring.
  6. Test semua animasi — meskipun backward compatibility dijaga, selalu lakukan regression test. Seriusan, jangan skip yang ini.

Semua logika animasi yang ditulis di Reanimated 2 atau 3 seharusnya berfungsi di 4.x dengan sedikit atau tanpa perubahan. Yang menarik, animasi berbasis shared values bisa hidup berdampingan dengan CSS animations — jadi kamu bisa mengadopsi API baru secara bertahap tanpa harus rewrite semuanya sekaligus.

FAQ: Pertanyaan yang Sering Diajukan

Apakah Reanimated 4 bisa digunakan dengan Expo Go?

Ya, Reanimated sudah termasuk di Expo Go untuk Expo SDK 55. Tapi karena Reanimated 4 butuh New Architecture, pastikan project kamu sudah mengaktifkannya di app.json dengan menambahkan "newArchEnabled": true. Untuk fitur yang lebih advanced atau custom native modules, pakai development build via EAS Build.

Apa perbedaan Animated API bawaan React Native dan Reanimated?

Animated API bawaan React Native menjalankan kalkulasi animasi di JavaScript thread, yang bisa menyebabkan frame drop kalau JS thread lagi sibuk. Reanimated menjalankan animasi di UI thread pakai worklets, sehingga animasi tetap smooth di 60 FPS meskipun JS thread sedang memproses data berat.

Selain itu, Reanimated 4 menawarkan API yang jauh lebih kaya — CSS animations, gesture integration, layout animations, shared element transitions. Jadi honestly, nggak ada alasan kuat untuk tetap pakai Animated API bawaan di project baru.

Apakah Reanimated 4 backward compatible dengan kode Reanimated 3?

Secara umum, iya. Semua logika animasi yang ditulis pakai Reanimated 2 atau 3 berfungsi di 4.x dengan perubahan minimal. Beberapa fungsi mengalami rename (seperti runOnJSscheduleOnRN), tapi nama lama masih jalan meskipun sudah deprecated. Breaking change utamanya: penghapusan dukungan Legacy Architecture dan pemisahan worklets ke library terpisah.

Bagaimana cara men-debug animasi Reanimated yang tidak berjalan?

Beberapa langkah troubleshooting yang bisa kamu coba:

  1. Pastikan komponen menggunakan Animated.View, bukan View biasa
  2. Periksa bahwa Babel plugin sudah terkonfigurasi dengan benar
  3. Bersihkan cache Metro dengan npx expo start --clear
  4. Pastikan shared value diakses via .value di dalam useAnimatedStyle
  5. Gunakan React Native DevTools untuk cek apakah ada error di console

Dari pengalaman, masalah nomor 1 dan 3 itu yang paling sering kejadian.

Bisakah saya menggunakan CSS Transitions dan Shared Values bersamaan?

Bisa banget! Kamu bisa pakai keduanya secara bersamaan bahkan dalam satu komponen. CSS Transitions dan CSS Keyframe Animations kompatibel dengan animasi berbasis shared values. Reanimated 4 memang dirancang supaya kamu bisa adopsi API CSS baru secara bertahap tanpa harus nulis ulang animasi yang sudah ada.

Tentang Penulis Editorial Team

Our team of expert writers and editors.