Компоненты/Раскрытие при прокрутке

Раскрытие при прокрутке

28 сентября 2026

Маленькая плашка с видео между двумя словами разрастается на весь экран по мере прокрутки.

Установка

npx astra-rn add scroll-zoom-reveal
pnpm dlx astra-rn add scroll-zoom-reveal
yarn dlx astra-rn add scroll-zoom-reveal
bunx --bun astra-rn add scroll-zoom-reveal

Файлы

index.tsx
НазадЛиния за прокруткойДалееПоиск потягом

Между двумя словами стоит небольшая плашка. При прокрутке она плавно растягивается до размеров всей сцены, а слова по бокам растворяются. Движение идёт через пружину Reanimated, поэтому остановка прокрутки не обрывает анимацию резко, а при прокрутке назад плашка сжимается до исходного размера.

Внутрь можно положить видео, картинку или любой другой элемент — он растянется по размеру рамки. Прокрутку даёт хук useZoomRevealScroll: подключите его onScroll к Animated.ScrollView и передайте scrollY в компонент. При «Уменьшении движения» пружина отключается. Работает на iOS, Android и в вебе.

Вручную

Поставьте зависимости:

npm install react-native-reanimated
pnpm add react-native-reanimated
yarn add react-native-reanimated
bun add react-native-reanimated

Скопируйте эти файлы в src/shared/components/organisms/scroll-zoom-reveal/:

Проверьте алиас путей. Импорты в коде выше записаны так, как их пишет astra-rn add: @/shared/components, @/shared/utils и @/shared/hooks. Нужен алиас "@/*": ["./src/*"] в tsconfig.json — или поправьте импорты под свои пути.

Использование

import React, { useState } from "react";
import { StyleSheet, Text, View, type LayoutChangeEvent } from "react-native";
import Animated from "react-native-reanimated";
import { VideoView, useVideoPlayer } from "expo-video";

import {
  ScrollZoomReveal,
  useZoomRevealScroll,
} from "@/shared/components/organisms/scroll-zoom-reveal";
import VIDEO from "~/assets/video/video-artist.mp4";
import { Showcase, useCustomize } from "~/showcase";


const CUSTOMIZE = [
  { key: "leading", label: "Слово слева", type: "text", default: "Над", maxLength: 24 },
  { key: "trailing", label: "Слово справа", type: "text", default: "морем", maxLength: 32 },
  { key: "pillWidth", label: "Ширина плашки", type: "slider", min: 32, max: 240, step: 4, unit: "px", default: 88 },
  { key: "pillHeight", label: "Высота плашки", type: "slider", min: 32, max: 240, step: 4, unit: "px", default: 88 },
  { key: "radius", label: "Скругление", type: "slider", min: 0, max: 60, step: 1, unit: "px", default: 24 },
  { key: "screens", label: "Длина прокрутки", type: "slider", min: 2, max: 6, step: 0.5, unit: "экр.", default: 3 },
] as const;

function ArtistVideo() {
  const player = useVideoPlayer(VIDEO, (instance) => {
    instance.loop = true;
    instance.muted = true;
    instance.play();
  });
  return (
    <VideoView
      player={player}
      style={styles.video}
      contentFit="cover"
      nativeControls={false}
      allowsPictureInPicture={false}
    />
  );
}

export default function ScrollZoomRevealScreen() {
  const custom = useCustomize(CUSTOMIZE);
  const { scrollY, onScroll } = useZoomRevealScroll();
  const [stage, setStage] = useState(0);

  const onLayout = (event: LayoutChangeEvent) => {
    const next = Math.round(event.nativeEvent.layout.height);
    setStage((prev) => (prev === next ? prev : next));
  };

  return (
    <Showcase>
      <View style={styles.page} onLayout={onLayout}>
        {stage > 0 ? (
          <Animated.ScrollView
            style={styles.scroller}
            onScroll={onScroll}
            scrollEventThrottle={16}
            showsVerticalScrollIndicator={false}
          >
            <ScrollZoomReveal
              scrollY={scrollY}
              height={custom.screens * stage}
              stageHeight={stage}
              leading={custom.leading}
              trailing={custom.trailing}
              leadingStyle={styles.leading}
              pillWidth={custom.pillWidth}
              pillHeight={custom.pillHeight}
              radius={custom.radius}
            >
              <ArtistVideo />
            </ScrollZoomReveal>
            <View style={[styles.after, { height: stage / 2 }]}>
              <Text style={styles.afterText}>Прокрутите обратно, чтобы снова уменьшить.</Text>
            </View>
          </Animated.ScrollView>
        ) : null}
      </View>
    </Showcase>
  );
}

const styles = StyleSheet.create({
  page: {
    flex: 1,
    backgroundColor: "#0a0a0a",
  },
  scroller: {
    flex: 1,
  },
  video: {
    width: "100%",
    height: "100%",
  },
  leading: {
    color: "#71717a",
  },
  after: {
    alignItems: "center",
    justifyContent: "center",
    padding: 24,
  },
  afterText: {
    fontSize: 14,
    lineHeight: 20,
    color: "rgba(250,250,250,0.44)",
  },
});

Пропсы

Пропс
Описание
scrollY

Тип SharedValue<number>

children

Тип ReactNode

leading?

Тип ReactNode

trailing?

Тип ReactNode

pillWidth?

Тип number

pillHeight?

Тип number

radius?

Тип number

height?

Тип number

stageHeight?

Тип number

viewportHeight?

Тип number

style?

Тип StyleProp<ViewStyle>

frameStyle?

Тип StyleProp<ViewStyle>

textStyle?

Тип StyleProp<TextStyle>

leadingStyle?

Тип StyleProp<TextStyle>

trailingStyle?

Тип StyleProp<TextStyle>