Каждое слово появляется по очереди: опускается сверху, чуть уменьшается и выходит из плотного размытия, словно объектив наводится на резкость. Задержку, шаг между словами, силу размытия и длительность можно настроить.
Анимация идёт на UI-потоке Reanimated. Размытие в вебе и на Android — настоящий фильтр, на iOS его имитирует мягкая тень текста. При «Уменьшении движения» текст просто плавно проявляется. Работает на iOS, Android и в вебе.
Поставьте зависимости:
npm install react-native-reanimatedpnpm add react-native-reanimatedyarn add react-native-reanimatedbun add react-native-reanimatedСкопируйте эти файлы в src/shared/components/organisms/cinematic-text/:
Проверьте алиас путей. Импорты в коде выше записаны так, как их пишет astra-rn add: @/shared/components, @/shared/utils и @/shared/hooks. Нужен алиас "@/*": ["./src/*"] в tsconfig.json — или поправьте импорты под свои пути.
import React from "react";
import { ScrollView, StyleSheet, useWindowDimensions, View } from "react-native";
import { CinematicText } from "@/shared/components/organisms/cinematic-text";
import { Showcase, useCustomize } from "~/showcase";
const CUSTOMIZE = [
{ key: "title", label: "Заголовок", type: "text", default: "Живые компоненты для новых интерфейсов.", maxLength: 80 },
{ key: "subtitle", label: "Подзаголовок", type: "text", default: "Каждое слово опускается из плотного размытия и плавно обретает резкость.", maxLength: 140 },
{ key: "titleSize", label: "Размер заголовка", type: "slider", min: 28, max: 96, step: 2, unit: "px", default: 60 },
{ key: "blur", label: "Размытие заголовка", type: "slider", min: 0, max: 60, step: 1, unit: "px", default: 28 },
{ key: "subtitleBlur", label: "Размытие подзаголовка", type: "slider", min: 0, max: 60, step: 1, unit: "px", default: 16 },
{ key: "stagger", label: "Шаг между словами", type: "slider", min: 0, max: 0.5, step: 0.01, unit: "с", default: 0.11 },
{ key: "duration", label: "Длительность", type: "slider", min: 0.3, max: 4, step: 0.1, unit: "с", default: 1.4 },
{ key: "delay", label: "Задержка заголовка", type: "slider", min: 0, max: 2, step: 0.05, unit: "с", default: 0.2 },
{ key: "subtitleDelay", label: "Задержка подзаголовка", type: "slider", min: 0, max: 3, step: 0.05, unit: "с", default: 0.85 },
] as const;
export default function CinematicTextScreen() {
const custom = useCustomize(CUSTOMIZE);
const { width } = useWindowDimensions();
const titleSize = custom.titleSize === 60 ? (width >= 640 ? 60 : 36) : custom.titleSize;
const replayKey = [custom.blur, custom.subtitleBlur, custom.stagger, custom.duration, custom.delay, custom.subtitleDelay].join(":");
return (
<Showcase>
<ScrollView contentContainerStyle={styles.scroll}>
<View key={replayKey} style={styles.stack}>
<CinematicText
delay={custom.delay}
stagger={custom.stagger}
blur={custom.blur}
duration={custom.duration}
containerStyle={{ maxWidth: titleSize * 8.4 }}
style={[
styles.title,
{
fontSize: titleSize,
lineHeight: titleSize * 0.95,
letterSpacing: -0.055 * titleSize,
},
]}
>
{custom.title}
</CinematicText>
<CinematicText
delay={custom.subtitleDelay}
stagger={custom.stagger}
blur={custom.subtitleBlur}
duration={custom.duration}
containerStyle={styles.subtitleBox}
style={styles.subtitle}
>
{custom.subtitle}
</CinematicText>
</View>
</ScrollView>
</Showcase>
);
}
const styles = StyleSheet.create({
scroll: {
flexGrow: 1,
alignItems: "center",
justifyContent: "center",
paddingVertical: 48,
},
stack: {
width: "100%",
alignItems: "center",
justifyContent: "center",
gap: 20,
paddingHorizontal: 24,
},
title: {
color: "#fafafa",
fontWeight: "600",
textAlign: "center",
},
subtitleBox: {
maxWidth: 14 * 0.55 * 42,
},
subtitle: {
color: "#a1a1aa",
fontSize: 14,
lineHeight: 24,
textAlign: "center",
},
});childrenТип string
delay?Тип number
stagger?Тип number
blur?Тип number
duration?Тип number
style?Тип StyleProp<TextStyle>
containerStyle?Тип StyleProp<ViewStyle>