В этой статье демонстрируется простой подход к созданию анимации со скоростью 60 кадров в секунду для заголовка списка прокрутки (например, изображения), когда вы выполняете эффект отскока, прокручивая вверх в приложениях iOS.

Я буду использовать Expo, поскольку с его помощью легко начать работу с React Native и использовать react-native-reanimated для создания красивых и плавных анимаций:

Установите зависимости и запустите проект:

npm i expo-cli

expo init my-app

expo install react-native-reanimated

Создайте базовый макет для анимации:

export default function App() {
  return (
    <View style={styles.container}>
      <Image
        style={styles.image}
        source={{ uri: 'http://picsum.photos/1000/1000' }}
      />
      <ScrollView contentContainerStyle={{ flexGrow: 1 }}>
        <View style={styles.items}>
          {new Array(15).fill(0).map((_, index) => <View key={index} style={styles.item} />)}
        </View>
      </ScrollView>
    </View>
  );
}
const IMAGE_HEIGHT = 300;
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#000',
  },
  items: {
    paddingTop: IMAGE_HEIGHT,
    paddingHorizontal: 8,
  },
  image: {
    ...StyleSheet.absoluteFillObject,
    top: 0,
    height: IMAGE_HEIGHT,
    width: '100%'
  },
  title: {
    color: '#FFF'
  },
  item: {
    backgroundColor: '#1C1C1C',
    height: 50,
    width: '100%',
    marginTop: 8,
  }
});

Давайте рассмотрим код!

  1. Как видите, я использую ScrollView и Image из react-native-reanimated для получения анимированных узлов.
  2. Значение свойства scrollEventThrottle равно 16 для узла ScrollView для ограничения вызова событий.
  3. useValue - хук для создания Animated.Value.
  4. Animated.event принимает массив сопоставлений и соответственно извлекает значения из каждого аргумента. В нашем случае мы извлекаем contentOffset.y и присваиваем его scrollY, поэтому значение scrollY на исходной стороне всегда совпадает с offsetY нашего Animated.ScrollView.
  5. Интерполяции offsetY из Animated.ScrollView для Animated.Image:
  • translateY нужен для перевода изображения вверх при прокрутке вниз
  • scale масштабирует изображение при прокрутке.
export default function App() {
  const scrollY = useValue(0)
  return (
    <View style={styles.container}>
      <Animated.Image
        style={{
          ...styles.image,
          transform: [
            {
              translateY: interpolate(scrollY, {
                inputRange: [0, IMAGE_WIDTH],
                outputRange: [0, -IMAGE_WIDTH],
                extrapolate: Extrapolate.CLAMP,
              }),
              scale: interpolate(scrollY, {
                inputRange: [-IMAGE_WIDTH * 2, 0],
                outputRange: [5, 1],
                extrapolate: Extrapolate.CLAMP,
              }),
            },
          ],
        }}
        source={{ uri: 'http://picsum.photos/1000/1000' }}
      />
      <Animated.ScrollView
        onScroll={Animated.event(
          [{ nativeEvent: { contentOffset: { y: scrollY } } }],
          { useNativeDriver: true }
        )}
        contentContainerStyle={{ flexGrow: 1 }}
        scrollEventThrottle={16}
      >
        <View style={styles.items}>
          {new Array(15).fill(0).map((_, index) => <View key={index} style={styles.item} />)}
        </View>
      </Animated.ScrollView>
    </View>
  );
}

Эффект анимации будет таким:

Надеюсь, с тобой все хорошо. Удачного кодирования! 🎉