Animations
React Native ships Animated + the modern Reanimated library for 60fps animations driven on the native UI thread. Use Reanimated 3+ — declarative, hooks-based, and far smoother than the legacy bridge.
Reanimated 3: spring, gesture, layout
EXAMPLE
// npm i react-native-reanimated react-native-gesture-handler
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming,
withRepeat,
withSequence,
interpolate,
Extrapolate,
runOnJS,
Layout,
FadeIn,
FadeOut,
} from 'react-native-reanimated';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import { View, StyleSheet, Text } from 'react-native';
import { useEffect } from 'react';
// 1) Basic — translate on mount
export function Slide() {
const x = useSharedValue(-100);
useEffect(() => { x.value = withSpring(0); }, []);
const style = useAnimatedStyle(() => ({
transform: [{ translateX: x.value }],
}));
return <Animated.View style={[styles.box, style]} />;
}
// 2) On press — sequence
export function PressMe() {
const scale = useSharedValue(1);
const onPress = () => {
scale.value = withSequence(
withTiming(1.2, { duration: 80 }),
withSpring(1),
);
};
const style = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }] }));
return (
<Animated.View style={[styles.btn, style]} onTouchEnd={onPress}>
<Text>Tap</Text>
</Animated.View>
);
}
// 3) Drag with Gesture Handler
export function Draggable() {
const tx = useSharedValue(0);
const ty = useSharedValue(0);
const offsetX = useSharedValue(0);
const offsetY = useSharedValue(0);
const pan = Gesture.Pan()
.onUpdate((e) => {
tx.value = offsetX.value + e.translationX;
ty.value = offsetY.value + e.translationY;
})
.onEnd(() => {
offsetX.value = tx.value;
offsetY.value = ty.value;
});
const style = useAnimatedStyle(() => ({
transform: [{ translateX: tx.value }, { translateY: ty.value }],
}));
return (
<GestureDetector gesture={pan}>
<Animated.View style={[styles.box, style]} />
</GestureDetector>
);
}
// 4) Interpolate — derive multiple styles from one shared value
export function ScrollHeader({ scrollY }) {
const style = useAnimatedStyle(() => {
const opacity = interpolate(scrollY.value, [0, 80], [1, 0], Extrapolate.CLAMP);
const height = interpolate(scrollY.value, [0, 80], [120, 56], Extrapolate.CLAMP);
return { opacity, height };
});
return <Animated.View style={[styles.header, style]} />;
}
// 5) Layout animations — list reflow for free
export function List({ items }) {
return items.map((it) => (
<Animated.View
key={it.id}
layout={Layout.springify()}
entering={FadeIn.duration(150)}
exiting={FadeOut.duration(150)}
>
<Text>{it.label}</Text>
</Animated.View>
));
}
// 6) Loops + repeat
const rot = useSharedValue(0);
useEffect(() => {
rot.value = withRepeat(withTiming(360, { duration: 1500 }), -1, false);
}, []);
// 7) Call JS from a worklet — runOnJS
const onLongDrag = (px) => Alert.alert(`dragged ${px}px`);
const pan = Gesture.Pan().onEnd((e) => {
if (Math.abs(e.translationX) > 150) runOnJS(onLongDrag)(e.translationX);
});
// 8) Tips
// • Use shared values, not React state, for animated values — avoids re-renders
// • Worklets run on the UI thread — must not touch React state directly (use runOnJS)
// • Use Layout + entering/exiting for declarative list animations
// • Animations run at native FPS — even during heavy JS work
const styles = StyleSheet.create({
box: { width: 80, height: 80, backgroundColor: 'crimson', borderRadius: 16 },
btn: { padding: 12, backgroundColor: '#eef', borderRadius: 8 },
header: { backgroundColor: '#111' },
});
Why it matters
Reanimated 3 worklets run on the UI thread, so animations don’t skip when the JS thread is busy. Pair with Gesture Handler for drag/swipe interactions that feel native — the legacy Animated + PanResponder combo is no contest.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';
const x = useSharedValue(0);
const style = useAnimatedStyle(() => ({ transform: [{ translateX: x.value }] }));
return <Animated.View style={[box, style]} onTouchEnd={() => x.value = withSpring(100)} />;
Try it Yourself »
Discussion
Loading…