Компоненты/Живой текст

Живой текст

31 января 2025

Текст проявляется посимвольно, с задержкой между знаками

Установка

npx astra-rn add animated-text
pnpm dlx astra-rn add animated-text
yarn dlx astra-rn add animated-text
bunx --bun astra-rn add animated-text

Файлы

index.tsx
НазадАэротрубаДалееКинотитры

Вручную

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

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

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

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

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

import { View, StyleSheet } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { StatusBar } from "expo-status-bar";
import { useFonts } from "expo-font";
import AnimatedText from "@/shared/components/organisms/animated-text";
import { useEffect, useRef, useState } from "react";
import { Showcase, useCustomize } from "~/showcase";

const CUSTOMIZE = [
  { key: "text1", label: "Фраза 1", type: "text", default: "Do you like this?", maxLength: 32 },
  { key: "text2", label: "Фраза 2", type: "text", default: "Isn't it awesome?", maxLength: 32 },
  { key: "text3", label: "Фраза 3", type: "text", default: "Astra makes it easy.", maxLength: 32 },
  { key: "text4", label: "Фраза 4", type: "text", default: "Enjoy using Astra!", maxLength: 32 },
  { key: "interval", label: "Смена фразы", type: "slider", min: 600, max: 5000, step: 100, unit: "ms", default: 1500 },
  { key: "fontSize", label: "Размер", type: "slider", min: 16, max: 56, step: 1, unit: "px", default: 30 },
  { key: "color", label: "Цвет текста", type: "color", default: "#ffffff" },
  { key: "background", label: "Фон", type: "color", default: "#0a0a0a" },
  { key: "charDelay", label: "Задержка букв", type: "slider", min: 0, max: 80, step: 1, unit: "ms", default: 15 },
  { key: "blur", label: "Размытие", type: "slider", min: 0, max: 100, step: 1, default: 50 },
  { key: "rise", label: "Подъём", type: "slider", min: 0, max: 120, step: 1, unit: "px", default: 55 },
  { key: "startScale", label: "Начальный масштаб", type: "slider", min: 0, max: 1, step: 0.05, default: 0.2 },
  { key: "damping", label: "Затухание", type: "slider", min: 5, max: 40, step: 1, default: 15 },
  { key: "stiffness", label: "Жёсткость", type: "slider", min: 50, max: 500, step: 10, default: 210 },
] as const;

export default function App() {
  const [fontLoaded] = useFonts({
    SfProRounded: require("@/assets/fonts/sf-pro-rounded.ttf"),
    HelveticaNowDisplay: require("@/assets/fonts/HelveticaNowDisplayMedium.ttf"),
    StretchPro: require("@/assets/fonts/StretchPro.otf"),
  });

  const custom = useCustomize(CUSTOMIZE);
  const TEXTS: string[] = [custom.text1, custom.text2, custom.text3, custom.text4];
  const textsRef = useRef<string[]>(TEXTS);
  textsRef.current = TEXTS;

  const [index, setIndex] = useState<number>(0);
  const text = TEXTS[index] ?? TEXTS[0];

  useEffect(() => {
    const interval = setInterval(() => {
      setIndex((current) => (current + 1) % textsRef.current.length);
    }, custom.interval);

    return () => clearInterval(interval);
  }, [custom.interval]);

  return (
    <Showcase>
      <View style={[styles.container, { backgroundColor: custom.background }]}>
        <StatusBar style="light" />
        <View>
          <AnimatedText
            text={text}
            animationConfig={{
              spring: {
                damping: custom.damping,
                stiffness: custom.stiffness,
                mass: 1,
              },
              characterDelay: custom.charDelay,
              maxBlurIntensity: custom.blur,
            }}
            enterFrom={{
              opacity: 0,
              translateY: custom.rise,
              scale: custom.startScale,
              rotate: 0,
            }}
            exitFrom={{
              opacity: 1,
              translateY: 0,
              scale: 1,
              rotate: 0,
            }}
            style={{
              color: custom.color,
              fontSize: custom.fontSize,
              fontFamily: fontLoaded ? "SfProRounded" : undefined,
            }}
          />
        </View>
      </View>
    </Showcase>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#0a0a0a",
    justifyContent: "center",
    alignItems: "center",
  },

  info: {
    padding: 24,
    backgroundColor: "rgba(0,0,0,0.5)",
  },
  year: {
    fontSize: 13,
    color: "rgba(255,255,255,0.6)",
    marginBottom: 6,
  },
  name: {
    fontSize: 20,
    color: "#fff",
    marginBottom: 4,
  },
  artist: {
    fontSize: 15,
    color: "rgba(255,255,255,0.7)",
  },
});

Пропсы

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

Тип string

style?

Тип StyleProp<TextStyle>

animationConfig?

Тип Partial<AnimationConfig>

enterFrom?

Тип Partial<CharacterAnimationParams>

enterTo?

Тип Partial<CharacterAnimationParams>

exitFrom?

Тип Partial<CharacterAnimationParams>

exitTo?

Тип Partial<CharacterAnimationParams>

CharacterProps

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

Тип string

index

Тип number

animationConfig

Тип AnimationConfig

enterFrom

Тип CharacterAnimationParams

enterTo

Тип CharacterAnimationParams

exitFrom

Тип CharacterAnimationParams

exitTo

Тип CharacterAnimationParams

style?

Тип StyleProp<TextStyle>

totalChars?

Тип number