คู่มือ React Native Reanimated 4: CSS Animations, Gesture Handler พร้อมโค้ดตัวอย่าง

เรียนรู้ React Native Reanimated 4 ตั้งแต่พื้นฐานถึงขั้นสูง ครอบคลุม CSS Transitions, Keyframe Animations, Shared Values และ Gesture Handler พร้อมโค้ดตัวอย่างที่ใช้งานได้จริงกับ Expo SDK 53+

บทนำ: ทำไมแอนิเมชัน 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, TooltipCSS Transitions
ขยาย/ยุบ AccordionCSS Transitions
เปลี่ยนสี, ขนาดตาม stateCSS Transitions
Loading spinner, pulseCSS Keyframe Animations
แอนิเมชันตาม gesture (ลาก, pinch)useAnimatedStyle + Shared Values
แอนิเมชันตาม scroll positionuseAnimatedStyle + Shared Values
แอนิเมชันที่ต้อง interpolate หลายค่าuseAnimatedStyle + Shared Values
Shared Element TransitionsReanimated 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 APIReanimated 4Moti
ThreadingJS thread (+ useNativeDriver)UI thread โดยตลอดUI thread (สร้างบน Reanimated)
CSS Transitionsไม่รองรับรองรับเต็มรูปแบบไม่รองรับ
Gesture Integrationจำกัดเต็มรูปแบบจำกัด
Bundle Size0 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)

ขั้นตอนหลัก

  1. อัปเกรดเป็น New Architecture: Reanimated 4 บังคับใช้ Fabric ต้องย้ายตรงนี้ก่อนเป็นอันดับแรก
  2. ติดตั้ง react-native-worklets: Worklets ถูกแยกเป็นแพ็คเกจอิสระ ต้องติดตั้งเพิ่ม
  3. อัปเดต Babel plugin: เปลี่ยนจาก react-native-reanimated/plugin เป็น react-native-worklets/plugin
  4. ตรวจสอบ 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 ให้อัตโนมัติ ไม่ต้องยุ่งกับอะไรเพิ่มเลย

เกี่ยวกับผู้เขียน Editorial Team

Our team of expert writers and editors.