บทนำ: ทำไมแอนิเมชัน 60 FPS ถึงสำคัญขนาดนี้
ลองนึกภาพแอปที่ทุกครั้งที่เลื่อนหน้าจอ เปิดเมนู หรือกดปุ่มแล้วมันกระตุก (janky) แม้แค่นิดเดียว ผู้ใช้จะรู้สึกได้ทันทีเลย แอนิเมชันคือหัวใจสำคัญของแอปมือถือที่ "ใช้แล้วรู้สึกดี" จริงๆ
ปัญหาคือ Animated API ที่มาพร้อมกับ React Native มีข้อจำกัดพอสมควร โดยเฉพาะเรื่องประสิทธิภาพ เพราะแอนิเมชันหลายตัวยังต้องพึ่ง JavaScript thread อยู่ พอ JS thread ยุ่งกับงานอื่น แอนิเมชันก็ตกเฟรมทันที
React Native Reanimated 4 (เวอร์ชันล่าสุด 4.2.2 ณ กุมภาพันธ์ 2026) เข้ามาแก้ปัญหานี้ได้อย่างสมบูรณ์ มันรันแอนิเมชันทั้งหมดบน UI thread โดยตรง และไฮไลท์สำคัญของเวอร์ชันนี้คือ CSS Animations และ Transitions ที่ทำให้เขียนแอนิเมชันง่ายเหมือนเขียน CSS บนเว็บเลย
ส่วนตัวผมเปลี่ยนมาใช้ Reanimated 4 ตั้งแต่ช่วง beta แล้ว ต้องบอกว่าประทับใจมากกับ CSS Transitions — มันลด boilerplate ไปได้เยอะมาก บทความนี้จะพาคุณเข้าใจ Reanimated 4 ตั้งแต่พื้นฐานจนถึงเทคนิคขั้นสูง พร้อมโค้ดตัวอย่างที่ใช้งานได้จริง
Reanimated 4 คืออะไร? ทำไมต้องอัปเกรด
React Native Reanimated เป็นไลบรารีแอนิเมชันประสิทธิภาพสูงจาก Software Mansion (ทีมเดียวกับที่สร้าง Gesture Handler และ React Native Screens) มียอดดาวน์โหลดกว่า 2.8 ล้านครั้งต่อสัปดาห์บน npm ซึ่งบอกได้เลยว่ามันเป็นมาตรฐานของวงการไปแล้ว
Reanimated 4 เปิดตัวเวอร์ชัน Stable ในเดือนกรกฎาคม 2025 และได้รับการอัปเดตอย่างต่อเนื่อง เวอร์ชันล่าสุด 4.2.2 รองรับ React Native 0.82+ และ Expo SDK 53+ อย่างสมบูรณ์
จุดเด่นหลักของ Reanimated 4
- CSS Animations และ Transitions: เขียนแอนิเมชันด้วย syntax คล้าย CSS ไม่ต้องเขียน boilerplate เยอะ
- Worklets แยกเป็นแพ็คเกจอิสระ:
react-native-workletsถูกแยกออกมา ทำให้ bundle size เล็กลงสำหรับคนที่ไม่ต้องใช้ทุกฟีเจอร์ - Shared Element Transitions: เพิ่มมาตั้งแต่ 4.2.0 รองรับ New Architecture เต็มรูปแบบ
- รองรับ 120 FPS บน iOS: ทำงานกับ ProMotion display ได้อย่างราบรื่น
- React Compiler friendly: รองรับ
shared.get()/shared.set()สำหรับ React Compiler
ข้อกำหนดสำคัญ
อันนี้ต้องเน้นไว้ก่อนเลย — Reanimated 4.x ทำงานได้เฉพาะกับ New Architecture (Fabric) เท่านั้น ถ้าแอปของคุณยังใช้สถาปัตยกรรมเก่าอยู่ ต้องย้ายไป New Architecture ก่อน หรือไม่ก็ใช้ Reanimated 3.x ต่อไป (แต่ 3.x จะไม่ได้รับการอัปเดตอีกแล้วนะ)
การติดตั้ง Reanimated 4 กับ Expo
สำหรับโปรเจกต์ Expo (SDK 53+) การติดตั้งทำได้ง่ายมากเลย:
npx expo install react-native-reanimated react-native-worklets
จากนั้นเพิ่ม Babel plugin ในไฟล์ babel.config.js:
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: [
'react-native-worklets/plugin', // ต้องอยู่ท้ายสุดเสมอ
],
};
};
สำหรับโปรเจกต์ React Native CLI ที่ไม่ใช้ Expo:
npm install react-native-reanimated react-native-worklets
แล้วก็เพิ่ม plugin ใน babel.config.js เหมือนกัน สิ่งสำคัญคือ plugin ต้องอยู่ตำแหน่งสุดท้ายของ array plugins เสมอ — อันนี้ลืมบ่อยมาก แล้วก็มานั่งหาบั๊กกันทีหลัง
CSS Transitions — แอนิเมชันแบบง่ายที่สุดใน Reanimated 4
ตรงนี้คือส่วนที่ผมชอบที่สุดเลย ฟีเจอร์ CSS Transitions ทำให้คุณสร้างแอนิเมชันได้แค่เปลี่ยน state แล้วกำหนด transition properties ใน style object โดยตรง ไม่ต้องยุ่งกับ useSharedValue หรือ useAnimatedStyle เลย
เอาจริงๆ ถ้าเคยเขียน CSS transitions บนเว็บมาก่อน จะรู้สึกเหมือนอยู่บ้านเลย
ตัวอย่าง: ปุ่มขยาย-ยุบพร้อมเปลี่ยนสี
import { useState } from 'react';
import { Pressable, Text, StyleSheet } from 'react-native';
import Animated from 'react-native-reanimated';
export default function ExpandButton() {
const [expanded, setExpanded] = useState(false);
return (
<Pressable onPress={() => setExpanded(!expanded)}>
<Animated.View
style={{
width: expanded ? 280 : 160,
height: expanded ? 80 : 48,
backgroundColor: expanded ? '#6ee7b7' : '#3b82f6',
borderRadius: expanded ? 16 : 24,
justifyContent: 'center',
alignItems: 'center',
// CSS Transitions ของ Reanimated 4
transitionProperty: ['width', 'height', 'backgroundColor', 'borderRadius'],
transitionDuration: 300,
transitionTimingFunction: 'ease-in-out',
}}
>
<Text style={styles.text}>
{expanded ? 'ขยายแล้ว' : 'กดเพื่อขยาย'}
</Text>
</Animated.View>
</Pressable>
);
}
const styles = StyleSheet.create({
text: { color: 'white', fontWeight: 'bold', fontSize: 16 },
});
สังเกตไหมครับ เราแค่เปลี่ยน state ด้วย setExpanded แล้ว Reanimated ก็จัดการแอนิเมชันระหว่างค่าเก่าและค่าใหม่ให้เองอัตโนมัติ ไม่ต้องเขียน withTiming หรือ withSpring เลย สะดวกจริงๆ
Transition Properties ที่ใช้ได้
transitionProperty— ระบุ property ที่ต้องการ animate (เช่น['width', 'opacity']หรือ'all')transitionDuration— ระยะเวลาแอนิเมชัน (มิลลิวินาที)transitionTimingFunction— ฟังก์ชัน easing เช่น'ease','ease-in','ease-out','ease-in-out','linear'transitionDelay— หน่วงเวลาก่อนเริ่มแอนิเมชัน (มิลลิวินาที)
CSS Keyframe Animations — แอนิเมชันที่ซับซ้อนและวนลูปได้
นอกจาก Transitions แล้ว Reanimated 4 ยังรองรับ CSS Keyframe Animations สำหรับแอนิเมชันที่ต้องกำหนดหลายจุด หรือต้องการให้วนลูปต่อเนื่อง พวก loading spinner หรือ skeleton screen นี่เหมาะมาก
ตัวอย่าง: Loading Spinner แบบ Pulse
import Animated from 'react-native-reanimated';
const pulseKeyframes = {
from: { opacity: 1, transform: [{ scale: 1 }] },
'50%': { opacity: 0.5, transform: [{ scale: 1.1 }] },
to: { opacity: 1, transform: [{ scale: 1 }] },
};
export default function PulseLoader() {
return (
<Animated.View
style={{
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: '#8b5cf6',
animationName: pulseKeyframes,
animationDuration: 1500,
animationIterationCount: 'infinite',
animationTimingFunction: 'ease-in-out',
}}
/>
);
}
ใช้ Preset จาก react-native-css-animations
Software Mansion ยังมีไลบรารีเสริมชื่อ react-native-css-animations ที่มี preset แอนิเมชันสำเร็จรูปให้ใช้งานได้ทันที ไม่ต้องเขียน keyframes เอง:
npx expo install react-native-css-animations
import { spin, bounce, pulse, shimmer } from 'react-native-css-animations';
import Animated from 'react-native-reanimated';
// หมุนต่อเนื่อง
<Animated.View style={[styles.icon, spin]} />
// เด้ง
<Animated.View style={[styles.badge, bounce]} />
// กะพริบ
<Animated.View style={[styles.skeleton, shimmer]} />
แค่นี้ก็ได้แอนิเมชันสวยๆ แล้ว ง่ายจนน่าตกใจ
Shared Values และ useAnimatedStyle — แนวทางคลาสสิกที่ยังทรงพลัง
โอเค CSS Transitions มันง่ายก็จริง แต่สำหรับแอนิเมชันที่ต้อง ควบคุมแบบ frame-by-frame เช่น แอนิเมชันตาม gesture, scroll position, หรือค่าจาก sensor ต่างๆ คุณยังต้องใช้ useSharedValue และ useAnimatedStyle อยู่ดี
useSharedValue คืออะไร?
useSharedValue คือ hook สำหรับเก็บค่าที่ใช้ร่วมกันระหว่าง JS thread และ UI thread ค่านี้อัปเดตบน UI thread ได้โดยตรง โดยไม่ trigger React re-render
นั่นแปลว่าแอนิเมชันรันที่ 60 FPS ได้แม้ JS thread จะยุ่งอยู่กับงานอื่น ซึ่งตรงนี้แหละที่ทำให้ Reanimated ต่างจาก Animated API แบบเดิมอย่างชัดเจน
ตัวอย่าง: Card แบบ Flip ด้วย Spring Animation
import { Pressable, Text, StyleSheet, View } from 'react-native';
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
interpolate,
} from 'react-native-reanimated';
export default function FlipCard() {
const rotation = useSharedValue(0);
const isFlipped = useSharedValue(false);
const handleFlip = () => {
isFlipped.value = !isFlipped.value;
rotation.value = withSpring(isFlipped.value ? 180 : 0, {
damping: 15,
stiffness: 100,
});
};
const frontStyle = useAnimatedStyle(() => ({
transform: [{ rotateY: `${rotation.value}deg` }],
backfaceVisibility: 'hidden',
opacity: interpolate(rotation.value, [0, 90, 180], [1, 0, 0]),
}));
const backStyle = useAnimatedStyle(() => ({
transform: [{ rotateY: `${rotation.value + 180}deg` }],
backfaceVisibility: 'hidden',
opacity: interpolate(rotation.value, [0, 90, 180], [0, 0, 1]),
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}));
return (
<Pressable onPress={handleFlip}>
<View style={styles.cardContainer}>
<Animated.View style={[styles.card, styles.cardFront, frontStyle]}>
<Text style={styles.cardText}>ด้านหน้า</Text>
</Animated.View>
<Animated.View style={[styles.card, styles.cardBack, backStyle]}>
<Text style={styles.cardText}>ด้านหลัง</Text>
</Animated.View>
</View>
</Pressable>
);
}
const styles = StyleSheet.create({
cardContainer: { width: 200, height: 280 },
card: {
width: '100%',
height: '100%',
borderRadius: 16,
justifyContent: 'center',
alignItems: 'center',
},
cardFront: { backgroundColor: '#3b82f6' },
cardBack: { backgroundColor: '#ef4444' },
cardText: { color: 'white', fontSize: 24, fontWeight: 'bold' },
});
เมื่อไหร่ควรใช้ CSS Transitions vs useAnimatedStyle?
อันนี้เป็นคำถามที่เจอบ่อยมาก เลยทำตารางเปรียบเทียบไว้ให้:
| สถานการณ์ | แนะนำให้ใช้ |
|---|---|
| เปิด/ปิด Modal, Tooltip | CSS Transitions |
| ขยาย/ยุบ Accordion | CSS Transitions |
| เปลี่ยนสี, ขนาดตาม state | CSS Transitions |
| Loading spinner, pulse | CSS Keyframe Animations |
| แอนิเมชันตาม gesture (ลาก, pinch) | useAnimatedStyle + Shared Values |
| แอนิเมชันตาม scroll position | useAnimatedStyle + Shared Values |
| แอนิเมชันที่ต้อง interpolate หลายค่า | useAnimatedStyle + Shared Values |
| Shared Element Transitions | Reanimated SharedTransition API |
การทำงานร่วมกับ React Native Gesture Handler
Reanimated 4 ทำงานร่วมกับ React Native Gesture Handler 2.30.0 ได้อย่างไร้รอยต่อ ทำให้สร้างแอนิเมชันที่ตอบสนองต่อ touch input ได้อย่างสมูทเต็ม 60 FPS ตรงนี้เป็นจุดที่ Reanimated ชนะขาดเลย
การติดตั้ง Gesture Handler
npx expo install react-native-gesture-handler
จากนั้นครอบแอปด้วย GestureHandlerRootView:
import { GestureHandlerRootView } from 'react-native-gesture-handler';
export default function App() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
{/* แอปของคุณ */}
</GestureHandlerRootView>
);
}
ตัวอย่าง: Drag and Drop Card
ตัวอย่างนี้เป็นการ์ดที่ลากได้ พร้อมเอฟเฟกต์ขยายเล็กน้อยตอนเริ่มลาก และมี momentum ตอนปล่อย:
import { StyleSheet, View, Text } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
withDecay,
} from 'react-native-reanimated';
export default function DraggableCard() {
const translateX = useSharedValue(0);
const translateY = useSharedValue(0);
const scale = useSharedValue(1);
const panGesture = Gesture.Pan()
.onStart(() => {
// ขยายเล็กน้อยเมื่อเริ่มลาก
scale.value = withSpring(1.05);
})
.onUpdate((event) => {
translateX.value = event.translationX;
translateY.value = event.translationY;
})
.onEnd((event) => {
// คืนขนาดปกติ
scale.value = withSpring(1);
// ใช้ withDecay เพื่อให้มี momentum
translateX.value = withDecay({ velocity: event.velocityX });
translateY.value = withDecay({ velocity: event.velocityY });
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{ translateX: translateX.value },
{ translateY: translateY.value },
{ scale: scale.value },
],
}));
return (
<View style={styles.container}>
<GestureDetector gesture={panGesture}>
<Animated.View style={[styles.card, animatedStyle]}>
<Text style={styles.text}>ลากฉันสิ!</Text>
</Animated.View>
</GestureDetector>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
card: {
width: 200,
height: 120,
backgroundColor: '#8b5cf6',
borderRadius: 16,
justifyContent: 'center',
alignItems: 'center',
elevation: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.3,
shadowRadius: 8,
},
text: { color: 'white', fontSize: 18, fontWeight: 'bold' },
});
ตัวอย่าง: Swipeable Delete Button
อีกตัวอย่างที่ใช้บ่อยมากในแอปจริงคือ swipe เพื่อลบ:
import { StyleSheet, View, Text, Dimensions } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
runOnJS,
} from 'react-native-reanimated';
const SWIPE_THRESHOLD = -80;
export default function SwipeableRow({ onDelete, children }) {
const translateX = useSharedValue(0);
const panGesture = Gesture.Pan()
.activeOffsetX([-10, 10])
.onUpdate((event) => {
// อนุญาตเฉพาะ swipe ไปทางซ้าย
translateX.value = Math.min(0, event.translationX);
})
.onEnd(() => {
if (translateX.value < SWIPE_THRESHOLD) {
translateX.value = withSpring(-100);
} else {
translateX.value = withSpring(0);
}
});
const rowStyle = useAnimatedStyle(() => ({
transform: [{ translateX: translateX.value }],
}));
return (
<View style={styles.rowContainer}>
<View style={styles.deleteButton}>
<Text style={styles.deleteText}>ลบ</Text>
</View>
<GestureDetector gesture={panGesture}>
<Animated.View style={[styles.row, rowStyle]}>
{children}
</Animated.View>
</GestureDetector>
</View>
);
}
const styles = StyleSheet.create({
rowContainer: { overflow: 'hidden' },
deleteButton: {
position: 'absolute',
right: 0,
top: 0,
bottom: 0,
width: 100,
backgroundColor: '#ef4444',
justifyContent: 'center',
alignItems: 'center',
},
deleteText: { color: 'white', fontWeight: 'bold' },
row: { backgroundColor: 'white' },
});
เทคนิคเพิ่มประสิทธิภาพแอนิเมชัน
แม้ Reanimated 4 จะรันบน UI thread แล้ว แต่ก็ยังมีเทคนิคเพิ่มเติมที่ช่วยให้แอนิเมชันสมูทขึ้นอีก มาดูกัน
1. ใช้ non-layout properties เป็นหลัก
การ animate properties ที่ไม่กระทบ layout อย่าง transform, opacity, backgroundColor จะเร็วกว่าพวก width, height, margin, padding อย่างเห็นได้ชัด เพราะไม่ต้องคำนวณ layout ใหม่ทุกเฟรม
// ดี - ใช้ transform แทน top/left
const animatedStyle = useAnimatedStyle(() => ({
transform: [
{ translateX: position.value.x },
{ translateY: position.value.y },
],
}));
// ไม่แนะนำ - ต้องคำนวณ layout ใหม่ทุกเฟรม
const animatedStyle = useAnimatedStyle(() => ({
left: position.value.x,
top: position.value.y,
}));
2. เปิด Feature Flags สำหรับประสิทธิภาพสูงสุด
Reanimated 4.2.0+ มี feature flags ที่ช่วยเพิ่มประสิทธิภาพได้อีก:
USE_COMMIT_HOOK_ONLY_FOR_REACT_COMMITS— แก้ปัญหา FPS drop ระหว่าง scrolling เมื่อมี animated components เยอะๆ (ต้องใช้ RN 0.80+)ANDROID_SYNCHRONOUSLY_UPDATE_UI_PROPSและIOS_SYNCHRONOUSLY_UPDATE_UI_PROPS— เปิด fast path สำหรับอัปเดต non-layout styles
3. เปิด 120 FPS บน iOS
สำหรับอุปกรณ์ที่รองรับ ProMotion display (iPhone 13 Pro ขึ้นไป) ให้แน่ใจว่า flag CADisableMinimumFrameDurationOnPhone ถูกเปิดใน Info.plist ข่าวดีคือมันเป็นค่าเริ่มต้นตั้งแต่ RN 0.82 แล้ว
4. ใช้ Skia สำหรับแอนิเมชันกราฟิกที่ซับซ้อน
สำหรับแอนิเมชันที่ซับซ้อนมากๆ อย่าง particle effects, custom drawing หรือ shader ให้ลองใช้ react-native-skia ร่วมกับ Reanimated จะดีกว่าการ render React components จำนวนมาก
5. ตรวจสอบ FPS ด้วย Performance Monitor
เปิด Performance Monitor ผ่าน Dev Menu (Cmd+D บน iOS Simulator, Cmd+M บน Android Emulator) แล้วดูตัวเลข 2 ค่านี้:
- JS FPS: ความเร็ว JavaScript thread
- UI FPS: ความเร็ว UI thread (แอนิเมชัน Reanimated รันตรงนี้)
ถ้า UI FPS อยู่ที่ 60 (หรือ 120 บน ProMotion) แสดงว่าแอนิเมชันสมูท ถ้าต่ำกว่านั้นแสดงว่ากำลังตกเฟรมอยู่ ซึ่งต้องกลับไปดูว่า animate property อะไรที่หนักเกินไป
เปรียบเทียบ Animated API, Reanimated 4 และ Moti
หลายคนสงสัยว่าควรเลือกตัวไหนดี เลยทำตารางเปรียบเทียบไว้ให้:
| คุณสมบัติ | Animated API | Reanimated 4 | Moti |
|---|---|---|---|
| Threading | JS thread (+ useNativeDriver) | UI thread โดยตลอด | UI thread (สร้างบน Reanimated) |
| CSS Transitions | ไม่รองรับ | รองรับเต็มรูปแบบ | ไม่รองรับ |
| Gesture Integration | จำกัด | เต็มรูปแบบ | จำกัด |
| Bundle Size | 0 KB (มาพร้อม RN) | ~120 KB | ~30 KB (+ Reanimated) |
| Learning Curve | ง่าย | ปานกลาง-สูง | ง่าย |
| New Architecture | รองรับ | บังคับ (v4) | รองรับ |
| 120 FPS | จำกัด | เต็มรูปแบบ | ผ่าน Reanimated |
| Shared Element Transitions | ไม่รองรับ | รองรับ (4.2.0+) | ไม่รองรับ |
สรุปง่ายๆ: สำหรับแอปในปี 2026 ที่ใช้ New Architecture แล้ว ให้ใช้ Reanimated 4 เป็นตัวเลือกหลัก ใช้ CSS Transitions สำหรับแอนิเมชันง่ายๆ และใช้ Shared Values + Gesture Handler สำหรับ interaction ที่ซับซ้อน ส่วน Moti เหมาะกับคนที่ต้องการ API ที่เรียบง่ายกว่าแต่ก็ต้องติดตั้ง Reanimated เป็น dependency อยู่ดี
ตัวอย่างโปรเจกต์จริง: Animated Bottom Sheet
เอาล่ะ มาดูตัวอย่างที่รวมทุกอย่างเข้าด้วยกัน — Shared Values, Gesture Handler, spring animations และ interpolation ทั้งหมดในคอมโพเนนต์เดียว:
import { StyleSheet, View, Text, Dimensions } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
interpolate,
Extrapolation,
} from 'react-native-reanimated';
const { height: SCREEN_HEIGHT } = Dimensions.get('window');
const MAX_TRANSLATE_Y = -SCREEN_HEIGHT + 80;
export default function BottomSheet() {
const translateY = useSharedValue(0);
const context = useSharedValue({ y: 0 });
const panGesture = Gesture.Pan()
.onStart(() => {
context.value = { y: translateY.value };
})
.onUpdate((event) => {
translateY.value = Math.max(
MAX_TRANSLATE_Y,
Math.min(0, context.value.y + event.translationY)
);
})
.onEnd((event) => {
// Snap to positions
if (translateY.value > -SCREEN_HEIGHT / 3) {
translateY.value = withSpring(0, { damping: 20 });
} else if (translateY.value > -SCREEN_HEIGHT / 1.5) {
translateY.value = withSpring(-SCREEN_HEIGHT / 2, { damping: 20 });
} else {
translateY.value = withSpring(MAX_TRANSLATE_Y, { damping: 20 });
}
});
const sheetStyle = useAnimatedStyle(() => ({
transform: [{ translateY: translateY.value }],
}));
const backdropStyle = useAnimatedStyle(() => ({
opacity: interpolate(
translateY.value,
[0, MAX_TRANSLATE_Y],
[0, 0.5],
Extrapolation.CLAMP
),
pointerEvents: translateY.value < -50 ? 'auto' : 'none',
}));
return (
<View style={styles.wrapper}>
<Animated.View style={[styles.backdrop, backdropStyle]} />
<GestureDetector gesture={panGesture}>
<Animated.View style={[styles.sheet, sheetStyle]}>
<View style={styles.handle} />
<Text style={styles.title}>Bottom Sheet</Text>
<Text style={styles.content}>
ลากขึ้นเพื่อขยาย ลากลงเพื่อย่อ
</Text>
</Animated.View>
</GestureDetector>
</View>
);
}
const styles = StyleSheet.create({
wrapper: { flex: 1 },
backdrop: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'black',
},
sheet: {
height: SCREEN_HEIGHT,
width: '100%',
backgroundColor: 'white',
position: 'absolute',
top: SCREEN_HEIGHT,
borderTopLeftRadius: 24,
borderTopRightRadius: 24,
paddingHorizontal: 20,
},
handle: {
width: 40,
height: 4,
backgroundColor: '#ccc',
borderRadius: 2,
alignSelf: 'center',
marginTop: 12,
marginBottom: 20,
},
title: { fontSize: 20, fontWeight: 'bold', marginBottom: 10 },
content: { fontSize: 16, color: '#666' },
});
การ Migrate จาก Reanimated 3 ไปยัง Reanimated 4
สำหรับคนที่ใช้ Reanimated 3 อยู่ ข่าวดีคือการอัปเกรดไม่ยากอย่างที่คิด API ส่วนใหญ่ยังคงเข้ากันได้ (backward compatible)
ขั้นตอนหลัก
- อัปเกรดเป็น New Architecture: Reanimated 4 บังคับใช้ Fabric ต้องย้ายตรงนี้ก่อนเป็นอันดับแรก
- ติดตั้ง react-native-worklets: Worklets ถูกแยกเป็นแพ็คเกจอิสระ ต้องติดตั้งเพิ่ม
- อัปเดต Babel plugin: เปลี่ยนจาก
react-native-reanimated/pluginเป็นreact-native-worklets/plugin - ตรวจสอบ API ที่เปลี่ยน: มีการเปลี่ยนชื่อเล็กน้อย ดูรายละเอียดใน migration guide ของ Software Mansion
// Reanimated 3 - babel.config.js
plugins: ['react-native-reanimated/plugin']
// Reanimated 4 - babel.config.js
plugins: ['react-native-worklets/plugin']
พูดตรงๆ โค้ดแอนิเมชันที่เขียนด้วย Reanimated v2 หรือ v3 API ส่วนใหญ่ทำงานได้กับ 4.x โดยแทบไม่ต้องแก้อะไรเลย ส่วนที่ต้องเปลี่ยนจริงๆ แค่ Babel plugin กับการติดตั้ง worklets เพิ่ม
คำถามที่พบบ่อย (FAQ)
Reanimated 4 ใช้กับ React Native เวอร์ชันเก่าได้ไหม?
ไม่ได้ครับ Reanimated 4.x รองรับเฉพาะ New Architecture (Fabric) และ React Native 3 เวอร์ชันล่าสุดเท่านั้น (ปัจจุบันคือ 0.80, 0.81 และ 0.82) ถ้าแอปยังไม่ได้ย้ายไป New Architecture ก็ใช้ Reanimated 3.x ไปก่อน แต่ควรวางแผนการย้ายไว้เพราะ 3.x จะไม่ได้รับการอัปเดตอีก
CSS Transitions ของ Reanimated 4 ต่างจาก CSS บนเว็บอย่างไร?
Syntax คล้ายกันมากเลย แต่มีความแตกต่างบางจุด เช่น transitionDuration ใน Reanimated ใช้ตัวเลข (มิลลิวินาที) แทนสตริง และ transitionProperty รับ array ของชื่อ property ข้อดีคือถ้าคุณคุ้นเคยกับ CSS อยู่แล้ว จะเรียนรู้ได้เร็วมาก
Reanimated 4 กับ Animated API ตัวไหนดีกว่า?
ขึ้นอยู่กับโปรเจกต์ สำหรับแอนิเมชันง่ายๆ อย่าง fade in/out หรือ slide เล็กๆ น้อยๆ Animated API ก็เพียงพอ แต่ถ้าต้องการ gesture-driven animations, 60-120 FPS ที่เสถียร หรือ CSS Transitions ที่สะดวกกว่า Reanimated 4 ดีกว่าอย่างชัดเจน สำหรับแอปใหม่ในปี 2026 ที่ใช้ New Architecture แนะนำเริ่มด้วย Reanimated 4 เลย
ต้องติดตั้ง react-native-worklets แยกต่างหากจริงหรือ?
ใช่ครับ ตั้งแต่ Reanimated 4 ทีม Software Mansion ได้แยก worklets ออกมาเป็นแพ็คเกจ react-native-worklets เพื่อให้ไลบรารีอื่นใช้งาน worklets ได้โดยไม่ต้องพึ่ง Reanimated ทั้งหมด แล้วก็ยังทำให้ bundle size เล็กลงด้วย เพียงแค่ติดตั้งและเพิ่ม Babel plugin ก็เรียบร้อย
ใช้ Reanimated 4 กับ Expo ได้เลยไหม ต้อง eject ก่อนหรือเปล่า?
ใช้ได้เลยครับ ไม่ต้อง eject! Reanimated 4 รองรับ Expo SDK 53+ อย่างสมบูรณ์ แค่ใช้คำสั่ง npx expo install react-native-reanimated react-native-worklets ก็พร้อมใช้งาน Expo จะจัดการ native configuration ให้อัตโนมัติ ไม่ต้องยุ่งกับอะไรเพิ่มเลย