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
| Skenario | API yang Direkomendasikan |
|---|---|
| Toggle, modal, expand/collapse | CSS Transitions |
| Animasi multi-tahap (loading, onboarding) | CSS Keyframe Animations |
| Drag, swipe, pinch, rotate | Gesture Handler + Shared Values |
| Header collapse, parallax | useAnimatedScrollHandler + Shared Values |
| Animasi antar-layar | Shared Element Transitions |
| Animasi masuk/keluar komponen | Layout 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:
- Pastikan aplikasi sudah menggunakan New Architecture — Reanimated 4 nggak mendukung Legacy Architecture sama sekali.
- Install
react-native-workletssebagai dependency terpisah. - Update Babel config — ganti
react-native-reanimated/pluginjadireact-native-worklets/plugin(untuk bare workflow). - Update nama fungsi —
runOnJS→scheduleOnRN,runOnUI→scheduleOnUI, dll. Nama lama masih berfungsi tapi deprecated. - Hapus parameter
restDisplacementThresholddanrestSpeedThresholddari konfigurasiwithSpring. - 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 runOnJS → scheduleOnRN), 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:
- Pastikan komponen menggunakan
Animated.View, bukanViewbiasa - Periksa bahwa Babel plugin sudah terkonfigurasi dengan benar
- Bersihkan cache Metro dengan
npx expo start --clear - Pastikan shared value diakses via
.valuedi dalamuseAnimatedStyle - 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.