Reanimated는 UI 스레드에서 직접 애니메이션을 실행하여 Animated API보다 뛰어난 성능과 유연성을 제공합니다.

Animated API가 기본 애니메이션에 충분하다면, Reanimated는 복잡한 제스처 기반 인터랙션, 레이아웃 애니메이션, 조건부 애니메이션에 필요합니다.


설치

BASH
npm install react-native-reanimated

# babel.config.js에 플러그인 추가
module.exports = {
  plugins: ['react-native-reanimated/plugin'],
};

핵심 개념: Shared Values와 Worklets

TSX
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withTiming,
  withSpring,
} from 'react-native-reanimated';

function BasicAnimation() {
  // Shared Value — JS와 UI 스레드 간 공유되는 값
  const offset = useSharedValue(0);

  // Animated Style — UI 스레드에서 실행되는 스타일 계산
  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ translateX: offset.value }],
  }));

  const moveRight = () => {
    // withTiming — 시간 기반 애니메이션
    offset.value = withTiming(200, { duration: 500 });
  };

  const moveBack = () => {
    // withSpring — 스프링 물리 애니메이션
    offset.value = withSpring(0);
  };

  return (
    <View>
      <Animated.View style={[styles.box, animatedStyle]} />
      <Pressable onPress={moveRight}><Text>오른쪽</Text></Pressable>
      <Pressable onPress={moveBack}><Text>되돌리기</Text></Pressable>
    </View>
  );
}

Animated API와 달리 Reanimated는 useRef로 값을 만들지 않고 useSharedValue를 사용 합니다. 이 값은 UI 스레드에서 직접 읽고 쓸 수 있어 Bridge를 거치지 않습니다.


제스처 기반 애니메이션

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

function DraggableBox() {
  const translateX = useSharedValue(0);
  const translateY = useSharedValue(0);
  const startX = useSharedValue(0);
  const startY = useSharedValue(0);

  const panGesture = Gesture.Pan()
    .onBegin(() => {
      startX.value = translateX.value;
      startY.value = translateY.value;
    })
    .onUpdate((event) => {
      translateX.value = startX.value + event.translationX;
      translateY.value = startY.value + event.translationY;
    })
    .onEnd(() => {
      // 손을 떼면 원래 위치로 돌아감
      translateX.value = withSpring(0);
      translateY.value = withSpring(0);
    });

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

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

Layout Animations

컴포넌트가 추가, 제거, 레이아웃이 변경될 때 자동 애니메이션을 적용합니다.

TSX
import Animated, {
  FadeIn,
  FadeOut,
  SlideInRight,
  SlideOutLeft,
  Layout,
} from 'react-native-reanimated';

function AnimatedList() {
  const [items, setItems] = useState<string[]>([]);

  const addItem = () => {
    setItems((prev) => [...prev, `아이템 ${prev.length + 1}`]);
  };

  const removeItem = (index: number) => {
    setItems((prev) => prev.filter((_, i) => i !== index));
  };

  return (
    <View>
      <Pressable onPress={addItem}><Text>추가</Text></Pressable>

      {items.map((item, index) => (
        <Animated.View
          key={item}
          entering={SlideInRight.duration(300)}   // 등장 애니메이션
          exiting={SlideOutLeft.duration(200)}     // 퇴장 애니메이션
          layout={Layout.springify()}              // 레이아웃 변경 애니메이션
          style={styles.listItem}
        >
          <Text>{item}</Text>
          <Pressable onPress={() => removeItem(index)}>
            <Text>삭제</Text>
          </Pressable>
        </Animated.View>
      ))}
    </View>
  );
}

사용 가능한 Layout Animation

TSX
// 등장 (Entering)
FadeIn, FadeInDown, FadeInUp, FadeInLeft, FadeInRight
SlideInDown, SlideInUp, SlideInLeft, SlideInRight
ZoomIn, BounceIn, FlipInXDown, RotateInDownLeft

// 퇴장 (Exiting)
FadeOut, FadeOutDown, SlideOutLeft, ZoomOut, BounceOut

// 커스터마이징
FadeIn.duration(500).delay(200).springify()
SlideInRight.springify().damping(15)

Interpolation

TSX
import { interpolate, Extrapolation } from 'react-native-reanimated';

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

  const headerStyle = useAnimatedStyle(() => ({
    height: interpolate(
      scrollY.value,
      [0, 200],
      [300, 80],
      Extrapolation.CLAMP
    ),
    opacity: interpolate(
      scrollY.value,
      [0, 150],
      [1, 0],
      Extrapolation.CLAMP
    ),
  }));

  return (
    <>
      <Animated.View style={[styles.header, headerStyle]}>
        <Text>패럴랙스 헤더</Text>
      </Animated.View>
      <Animated.ScrollView
        onScroll={useAnimatedScrollHandler({
          onScroll: (event) => {
            scrollY.value = event.contentOffset.y;
          },
        })}
        scrollEventThrottle={16}
      >
        {/* 콘텐츠 */}
      </Animated.ScrollView>
    </>
  );
}

Animated API vs Reanimated 비교

항목Animated APIReanimated
실행 위치네이티브 드라이버 선택적항상 UI 스레드
레이아웃 애니메이션미지원지원
제스처 연동제한적Gesture Handler 통합
조건부 로직불가worklet 내 if/else 가능
설치내장별도 설치
학습 곡선낮음중간

정리

  • Reanimated는 UI 스레드에서 직접 애니메이션을 실행 하여 항상 60fps를 보장합니다
  • useSharedValueuseAnimatedStyle이 핵심 API입니다
  • Layout Animations 로 리스트 추가/삭제 시 자동 애니메이션을 적용할 수 있습니다
  • Gesture Handler 와 결합하면 드래그, 스와이프 같은 복잡한 인터랙션을 구현할 수 있습니다
  • 기본 애니메이션은 Animated API로 충분하지만, 복잡해지면 Reanimated가 필수입니다
댓글 로딩 중...