React Native / React Native Reanimated

React Native Reanimated

Суурь Animated API-аас илүү гөлгөр, хурдан animation хийхэд react-native-reanimated хэрэглэнэ. Гол давуу тал нь animation бүхэлдээ UI thread дээр ажилладаг тул JavaScript thread удааширсан ч animation тасалдахгүй. Орчин үеийн аппуудад Reanimated бол стандарт болсон.

Суулгах ба тохируулга

bash
npx expo install react-native-reanimated

babel.config.js файлд plugin нэмнэ — энэ алхмыг орхивол ажиллахгүй:

javascript
module.exports = function (api) {
  api.cache(true);
  return {
    presets: ["babel-preset-expo"],
    plugins: ["react-native-reanimated/plugin"],
  };
};

Тохируулсаны дараа npx expo start --clear ажиллуулж cache цэвэрлэнэ.

useSharedValue ба useAnimatedStyle

Reanimated-н гол ойлголт хоёр hook дээр суурилдаг:

typescript
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withTiming,
  withSpring,
} from 'react-native-reanimated';
import { TouchableOpacity, View, Text, StyleSheet } from 'react-native';

export default function ReanimatedExample() {
  // useSharedValue нь UI thread дээр ажилладаг утга
  const scale = useSharedValue(1);
  const opacity = useSharedValue(1);

  // useAnimatedStyle нь shared value-г style болгоно
  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }],
    opacity: opacity.value,
  }));

  function handlePress() {
    scale.value = withSpring(1.2, {}, () => {
      scale.value = withSpring(1);
    });
    opacity.value = withTiming(0.7, { duration: 150 }, () => {
      opacity.value = withTiming(1, { duration: 150 });
    });
  }

  return (
    <View style={styles.container}>
      <TouchableOpacity onPress={handlePress}>
        <Animated.View style={[styles.card, animatedStyle]}>
          <Text style={styles.emoji}>🚀</Text>
          <Text style={styles.label}>Дар!</Text>
        </Animated.View>
      </TouchableOpacity>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#0b1120' },
  card: { width: 140, height: 140, backgroundColor: '#0f172a', borderRadius: 20, justifyContent: 'center', alignItems: 'center', borderWidth: 1, borderColor: '#22d3ee' },
  emoji: { fontSize: 48 },
  label: { color: '#94a3b8', marginTop: 8 },
});

useSharedValue нь useRef(new Animated.Value()) шиг боловч UI thread дээр шууд ажилладаг. useAnimatedStyle дотор scale.value гэж утгыг унших нь автоматаар reactive болдог.

Scroll-тай хослуулсан animation

Scroll хийхэд header жижгэрэх, алга болох animation маш түгээмэл:

typescript
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  useAnimatedScrollHandler,
  interpolate,
  Extrapolation,
} from 'react-native-reanimated';

const HEADER_HEIGHT = 200;

export default function ParallaxScreen() {
  const scrollY = useSharedValue(0);

  const scrollHandler = useAnimatedScrollHandler({
    onScroll: (event) => {
      scrollY.value = event.contentOffset.y;
    },
  });

  const headerStyle = useAnimatedStyle(() => {
    const height = interpolate(
      scrollY.value,
      [0, HEADER_HEIGHT],
      [HEADER_HEIGHT, 60],
      Extrapolation.CLAMP
    );
    const opacity = interpolate(
      scrollY.value,
      [0, HEADER_HEIGHT / 2],
      [1, 0],
      Extrapolation.CLAMP
    );
    return { height, opacity };
  });

  return (
    <View style={{ flex: 1, backgroundColor: '#0b1120' }}>
      <Animated.View style={[{ backgroundColor: '#22d3ee', justifyContent: 'center', alignItems: 'center' }, headerStyle]}>
        <Text style={{ color: '#0b1120', fontSize: 22, fontWeight: 'bold' }}>Миний апп</Text>
      </Animated.View>

      <Animated.ScrollView onScroll={scrollHandler} scrollEventThrottle={16}>
        {Array.from({ length: 20 }, (_, i) => (
          <View key={i} style={{ padding: 16, borderBottomWidth: 1, borderBottomColor: '#1e293b' }}>
            <Text style={{ color: '#f1f5f9' }}>Жагсаалтын мөр {i + 1}</Text>
          </View>
        ))}
      </Animated.ScrollView>
    </View>
  );
}

withTiming, withSpring, withRepeat

Reanimated-д animation хийх олон арга бий:

typescript
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withTiming,
  withSpring,
  withRepeat,
  withSequence,
  Easing,
} from 'react-native-reanimated';

export default function AnimationTypesDemo() {
  const rotation = useSharedValue(0);

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ rotate: `${rotation.value}deg` }],
  }));

  function spin() {
    // Тогтмол эргэх
    rotation.value = withRepeat(
      withTiming(360, { duration: 1000, easing: Easing.linear }),
      -1,  // -1 = хязгааргүй давталт
      false
    );
  }

  function shake() {
    // Чичрэх
    rotation.value = withSequence(
      withTiming(-10, { duration: 50 }),
      withTiming(10, { duration: 50 }),
      withTiming(-10, { duration: 50 }),
      withTiming(0, { duration: 50 })
    );
  }

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#0b1120' }}>
      <Animated.View style={[{ width: 80, height: 80, backgroundColor: '#a78bfa', borderRadius: 12 }, animatedStyle]} />
      <TouchableOpacity onPress={spin} style={{ marginTop: 24, padding: 12, backgroundColor: '#1e293b', borderRadius: 8 }}>
        <Text style={{ color: '#f1f5f9' }}>Эргүүлэх</Text>
      </TouchableOpacity>
      <TouchableOpacity onPress={shake} style={{ marginTop: 8, padding: 12, backgroundColor: '#1e293b', borderRadius: 8 }}>
        <Text style={{ color: '#f1f5f9' }}>Чичрүүлэх</Text>
      </TouchableOpacity>
    </View>
  );
}

Reanimated маш хүчирхэг боловч суурь ойлголт нь энгийн — useSharedValue + useAnimatedStyle хоёрыг сайн ойлгосон бол бусад нь цаашид өөрөө ойлгогдоно.

Дараагийн хичээлд:

Gesture Handler — хуруугаараа дарах, чирэх, pinch zoom хийх зэрэг gesture-үүдийг animation-тай хослуулах талаар сурна. Reanimated болон Gesture Handler хамтдаа ашиглахад апп маш мэргэжлийн болдог.