Animated API — 모달 & 피커 애니메이션 (웹 CSS 비교)
개요
React Native에서 애니메이션을 구현하는 핵심 API인 Animated를 정리합니다.
BottomSheetModal(하단 모달)과 WheelPicker(드럼 롤 피커) 구현 과정에서 사용한 실제 코드 예시 포함.
1. Animated.Value (애니메이션 값)
웹 React
// CSS transition으로 처리
.modal { transition: transform 0.3s ease; }
.modal.open { transform: translateY(0); }
.modal.closed { transform: translateY(100%); }
React Native
import { Animated } from "react-native";
// useRef로 애니메이션 값 생성 (리렌더링해도 유지)
const slideAnim = useRef(new Animated.Value(0)).current;
const overlayAnim = useRef(new Animated.Value(0)).current;
핵심 포인트
Animated.Value(초기값)— 애니메이션에 사용할 값 생성useRef로 감싸야 리렌더링 시에도 같은 인스턴스 유지-
.current로 실제 값에 접근
2. Animated.timing & Animated.spring (구동 방식)
Animated.timing — 일정 시간 동안 변화
Animated.timing(overlayAnim, {
toValue: 1, // 목표값
duration: 250, // 밀리초
useNativeDriver: true, // 네이티브 스레드에서 실행 (성능↑)
}).start();
웹의
transition: opacity 250ms ease와 유사
Animated.spring — 스프링 물리 기반 변화
Animated.spring(slideAnim, {
toValue: 1,
damping: 20, // 감쇠 (높을수록 빨리 멈춤)
stiffness: 200, // 강성 (높을수록 빠르게 이동)
useNativeDriver: true,
}).start();
웹의
cubic-bezier보다 자연스러운 물리 기반 애니메이션
비교 테이블
| 방식 | 웹 CSS | React Native | 특징 |
|---|---|---|---|
| 시간 기반 | transition: 250ms | Animated.timing | 정확한 duration 제어 |
| 물리 기반 | cubic-bezier() | Animated.spring | 자연스러운 바운스 효과 |
3. Animated.parallel (동시 실행)
여러 애니메이션을 동시에 실행합니다.
// overlay fade-in + content slide-up 동시 실행
Animated.parallel([
Animated.timing(overlayAnim, {
toValue: 1,
duration: 250,
useNativeDriver: true,
}),
Animated.spring(slideAnim, {
toValue: 1,
damping: 20,
stiffness: 200,
useNativeDriver: true,
}),
]).start();
다른 조합 방식
| API | 동작 | 용도 |
|---|---|---|
| Animated.parallel | 동시 실행 | overlay + slide-up 동시 실행 |
| Animated.sequence | 순차 실행 | 먼저 fade → 그 다음 slide |
| Animated.stagger | 시차 실행 | 리스트 아이템 순차 등장 |
4. interpolate (값 변환)
애니메이션 값(0~1)을 실제 UI 속성값으로 변환합니다.
import { Dimensions } from "react-native";
const SCREEN_HEIGHT = Dimensions.get("window").height;
// 0 → 화면 아래, 1 → 제자리
const translateY = slideAnim.interpolate({
inputRange: [0, 1],
outputRange: [SCREEN_HEIGHT, 0],
});
// Animated.View에 적용
<Animated.View style={{ transform: [{ translateY }] }}>
{/* 모달 컨텐츠 */}
</Animated.View>
WheelPicker에서의 interpolate
// 스크롤 위치에 따른 아이템 크기/투명도 변화
const scale = scrollY.interpolate({
inputRange: [
(index - 2) * itemHeight,
(index - 1) * itemHeight,
index * itemHeight, // 선택된 위치
(index + 1) * itemHeight,
(index + 2) * itemHeight,
],
outputRange: [0.7, 0.85, 1, 0.85, 0.7],
extrapolate: "clamp", // 범위 밖은 최소/최대값 고정
});
웹 비교: CSS에는 스크롤 위치 기반 interpolation이 없어서 JS로
IntersectionObserver등을 써야 함
5. Animated.event (이벤트 연동)
스크롤 등의 네이티브 이벤트를 애니메이션 값에 직접 연결합니다.
<Animated.FlatList
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: scrollY } } }],
{ useNativeDriver: true } // JS 브릿지 안 거침 → 60fps
)}
// ...
/>
핵심 포인트
onScroll이벤트의contentOffset.y값이scrollY에 실시간 반영useNativeDriver: true→ JS 스레드를 거치지 않아 60fps 성능 보장-
웹에서는
onScroll+requestAnimationFrame으로 비슷하게 구현
6. Animated.View / Animated.FlatList (특수 컴포넌트)
// 일반 View 대신 Animated.View 사용
<Animated.View style={{ opacity: overlayAnim }}>
{/* fade 효과가 적용된 컨텐츠 */}
</Animated.View>
// FlatList에 애니메이션 적용 시
<Animated.FlatList
data={items}
renderItem={renderItem}
// ...
/>
중요: 일반
View,FlatList에는Animated.Value를 스타일로 전달할 수 없음. 반드시Animated.접두사가 붙은 컴포넌트 사용
7. useNativeDriver 제약사항
useNativeDriver: true는 성능이 좋지만 제약이 있습니다.
| 가능 | 불가능 |
|---|---|
| transform (translateX/Y, scale, rotate) | width, height |
| opacity | padding, margin |
| backgroundColor | |
| borderRadius |
팁: 레이아웃 속성을 애니메이션하려면
useNativeDriver: false로 설정하거나,react-native-reanimated라이브러리 사용
빠른 참조 테이블
| 항목 | 웹 CSS/JS | React Native Animated |
|---|---|---|
| 시간 기반 전환 | transition: 250ms | Animated.timing({ duration: 250 }) |
| 스프링 효과 | cubic-bezier() | Animated.spring({ damping, stiffness }) |
| 동시 실행 | CSS에서 자동 | Animated.parallel([]) |
| 값 변환 | 없음 (JS 계산) | .interpolate({ inputRange, outputRange }) |
| 스크롤 연동 | onScroll • rAF | Animated.event (네이티브 스레드) |
| 성능 최적화 | GPU 합성 (will-change) | useNativeDriver: true |