20. React Native Reanimated
Reanimated 3 — анимации полностью в нативном потоке. 60/120fps без JS. Замена встроенного Animated API для сложных анимаций.
Установка
Заголовок раздела «Установка»npx expo install react-native-reanimated// babel.config.js — плагин ОБЯЗАТЕЛЕН (в конце массива!)module.exports = { presets: ['babel-preset-expo'], plugins: ['react-native-reanimated/plugin'],};Основные hooks
Заголовок раздела «Основные hooks»import Animated, { useSharedValue, useAnimatedStyle, withTiming, withSpring, withRepeat, withSequence, runOnJS,} from 'react-native-reanimated';
const offset = useSharedValue(0);
// Анимацияoffset.value = withTiming(100, { duration: 300 });offset.value = withSpring(0, { damping: 10, stiffness: 100 });
// Стили на основе sharedValueconst animatedStyle = useAnimatedStyle(() => ({ transform: [{ translateX: offset.value }], opacity: offset.value / 100,}));
<Animated.View style={[styles.box, animatedStyle]} />Примеры
Заголовок раздела «Примеры»// Fade in при монтированииfunction FadeIn({ children }) { const opacity = useSharedValue(0); useEffect(() => { opacity.value = withTiming(1, { duration: 500 }); }, []); const style = useAnimatedStyle(() => ({ opacity: opacity.value })); return <Animated.View style={style}>{children}</Animated.View>;}
// Bounce кнопкаfunction BounceButton({ onPress, title }) { const scale = useSharedValue(1); const style = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }] }));
const handlePress = () => { scale.value = withSequence( withTiming(0.9, { duration: 80 }), withSpring(1, { damping: 3, stiffness: 200 }) ); runOnJS(onPress)(); };
return ( <AnimatedPressable style={style} onPress={handlePress}> <Text>{title}</Text> </AnimatedPressable> );}
// Shake (ошибка ввода)offset.value = withSequence( withTiming(-10, { duration: 60 }), withTiming(10, { duration: 60 }), withTiming(-10, { duration: 60 }), withSpring(0),);
// Pulse (бесконечная)scale.value = withRepeat( withSequence( withTiming(1.2, { duration: 300 }), withTiming(1, { duration: 300 }) ), -1, // -1 = бесконечно true // reverse);Scroll Animations
Заголовок раздела «Scroll Animations»import { useAnimatedScrollHandler } from 'react-native-reanimated';
function ParallaxHeader() { const scrollY = useSharedValue(0);
const scrollHandler = useAnimatedScrollHandler({ onScroll: (event) => { scrollY.value = event.contentOffset.y; }, });
const headerStyle = useAnimatedStyle(() => ({ transform: [{ translateY: scrollY.value * 0.5 }], opacity: 1 - scrollY.value / 200, }));
return ( <> <Animated.View style={[styles.header, headerStyle]} /> <Animated.ScrollView onScroll={scrollHandler} scrollEventThrottle={16}> {/* Контент */} </Animated.ScrollView> </> );}Entering / Exiting Animations
Заголовок раздела «Entering / Exiting Animations»import Animated, { FadeIn, FadeOut, SlideInRight, BounceIn } from 'react-native-reanimated';
// Автоматическая анимация появления/исчезновения<Animated.View entering={FadeIn.duration(500)} exiting={FadeOut}> <Text>Появлюсь с fade</Text></Animated.View>
<Animated.View entering={SlideInRight.delay(200)} exiting={FadeOut}> <Text>Слайд справа</Text></Animated.View>
<Animated.View entering={BounceIn} exiting={FadeOut}> <Text>Bounce!</Text></Animated.View>- Reanimated 3 работает полностью в нативном потоке
- Reanimated 3 работает полностью в нативном потоке
useSharedValueвместоAnimated.ValueuseAnimatedStyle— стили вычисляются нативноwithTiming,withSpring— анимацииwithSequence,withRepeat— комбинацииentering/exiting— декларативные layout анимацииrunOnJS()— вызов JS из нативного потока