Передайте в компонент scrollY — общее значение из useAnimatedScrollHandler вашего списка. Компонент сам измеряет своё положение на экране и считает прогресс: слова начинают загораться, когда верх текста пересекает линию start (доля высоты экрана), и загорается всё, когда низ текста доходит до линии end. softness — сколько слов одновременно находятся в полутени.
Работает с любым Animated.ScrollView или Animated.FlatList. Если список не на весь экран, укажите viewportTop и viewportHeight. Меняется только прозрачность слов — раскладка стабильна.
Поставьте зависимости:
npm install react-native-reanimatedpnpm add react-native-reanimatedyarn add react-native-reanimatedbun add react-native-reanimatedСкопируйте эти файлы в src/shared/components/molecules/scroll-lit-text/:
Проверьте алиас путей. Импорты в коде выше записаны так, как их пишет astra-rn add: @/shared/components, @/shared/utils и @/shared/hooks. Нужен алиас "@/*": ["./src/*"] в tsconfig.json — или поправьте импорты под свои пути.
import { useState } from "react";
import { StyleSheet, View, useWindowDimensions } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { StatusBar } from "expo-status-bar";
import { useFonts } from "expo-font";
import sfProRounded from "@/assets/fonts/sf-pro-rounded.ttf";
import Animated, {
scrollTo,
useAnimatedRef,
useAnimatedScrollHandler,
useDerivedValue,
useSharedValue,
withTiming,
Easing,
} from "react-native-reanimated";
import { ScrollLitText } from "@/shared/components/molecules/scroll-lit-text";
import { Showcase, useAutoplay, useCustomize } from "~/showcase";
const CUSTOMIZE = [
{ key: "text", label: "Текст", type: "text", default: "Хороший интерфейс не спорит с человеком. Он замечает паузу, подсказывает шаг и уходит в тень, когда дело сделано.", maxLength: 220 },
{ key: "fontSize", label: "Размер шрифта", type: "slider", min: 16, max: 44, step: 1, unit: "px", default: 30 },
{ key: "dimOpacity", label: "Яркость до", type: "slider", min: 0, max: 0.6, step: 0.01, default: 0.16 },
{ key: "softness", label: "Мягкость фронта", type: "slider", min: 0.2, max: 6, step: 0.1, unit: "сл.", default: 1.6 },
{ key: "color", label: "Цвет текста", type: "color", default: "#FAFAFA" },
{ key: "background", label: "Фон", type: "color", default: "#0A0A0A" },
] as const;
export default function App() {
const [fontLoaded] = useFonts({
SfProRounded: sfProRounded,
});
const custom = useCustomize(CUSTOMIZE);
const { height } = useWindowDimensions();
const scrollY = useSharedValue(0);
const scrollRef = useAnimatedRef<Animated.ScrollView>();
const driven = useSharedValue(-1);
const [textHeight, setTextHeight] = useState(200);
const target = Math.max(0, height * 0.9 - (height - textHeight) / 2);
const litEnd = Math.min(0.8, 0.5 + textHeight / (2 * height) + 0.04);
const onScroll = useAnimatedScrollHandler((event) => {
scrollY.value = event.contentOffset.y;
});
useDerivedValue(() => {
if (driven.value >= 0) scrollTo(scrollRef, 0, driven.value, false);
});
useAutoplay(async ({ wait }) => {
driven.value = 0;
await wait(500);
driven.value = withTiming(target, { duration: 3600, easing: Easing.inOut(Easing.cubic) });
await wait(4400);
driven.value = withTiming(0, { duration: 1400, easing: Easing.inOut(Easing.cubic) });
await wait(1800);
}, { startDelayMs: 300 });
return (
<Showcase>
<GestureHandlerRootView style={[styles.container, { backgroundColor: custom.background }]}>
<StatusBar style="light" />
<Animated.ScrollView
ref={scrollRef}
onScroll={onScroll}
scrollEventThrottle={16}
onTouchStart={() => {
driven.value = -1;
}}
contentContainerStyle={[styles.content, { paddingTop: height * 0.9, paddingBottom: height * 0.7 }]}
showsVerticalScrollIndicator={false}
>
<View onLayout={(event) => setTextHeight(event.nativeEvent.layout.height)}>
<ScrollLitText
text={custom.text}
scrollY={scrollY}
end={litEnd}
fontSize={custom.fontSize}
dimOpacity={custom.dimOpacity}
softness={custom.softness}
color={custom.color}
fontFamily={fontLoaded ? "SfProRounded" : undefined}
/>
</View>
<View style={styles.rule} />
</Animated.ScrollView>
</GestureHandlerRootView>
</Showcase>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: "#0A0A0A" },
content: { paddingHorizontal: 24, gap: 18 },
rule: { height: 1, backgroundColor: "#27272A", marginTop: 12 },
});textТип string
scrollYТип SharedValue<number>
start?Тип number
end?Тип number
viewportTop?Тип number
viewportHeight?Тип number
softness?Тип number
dimOpacity?Тип number
color?Тип string
fontSize?Тип number
lineHeight?Тип number
fontWeight?Тип TextStyle["fontWeight"]
fontFamily?Тип string
align?Тип ScrollLitTextAlign
style?Тип StyleProp<TextStyle>
containerStyle?Тип StyleProp<ViewStyle>