Компоненты/Текст Dia

Текст Dia

21 августа 2026

Текст проявляется слово за словом из рассеянного размытия

Установка

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

Файлы

index.tsx
НазадСтрока по дугеДалееТекст в пыль

Вручную

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

npm install @react-native-masked-view/masked-view expo-linear-gradient react-native-reanimated react-native-worklets
pnpm add @react-native-masked-view/masked-view expo-linear-gradient react-native-reanimated react-native-worklets
yarn add @react-native-masked-view/masked-view expo-linear-gradient react-native-reanimated react-native-worklets
bun add @react-native-masked-view/masked-view expo-linear-gradient react-native-reanimated react-native-worklets

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

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

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

import React, { useCallback, useMemo } from "react";
import { LayoutChangeEvent, Platform, StyleSheet, Text, View } from "react-native";
import { DiaText } from "@/shared/components/organisms/dia-text";
import { Showcase, useCustomize } from "~/showcase";
import Animated, {
  LinearTransition,
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from "react-native-reanimated";

const isWeb = Platform.OS === "web";

const CUSTOMIZE = [
  { key: "lead", label: "Начало фразы", type: "text", default: "Make every moment", maxLength: 40 },
  { key: "word1", label: "Слово 1", type: "text", default: "beautiful.", maxLength: 24 },
  { key: "word2", label: "Слово 2", type: "text", default: "effortless.", maxLength: 24 },
  { key: "word3", label: "Слово 3", type: "text", default: "memorable.", maxLength: 24 },
  { key: "baseColor", label: "Цвет текста", type: "color", default: "#f6f3ec" },
  { key: "fontSize", label: "Размер шрифта", type: "slider", min: 12, max: 40, step: 1, unit: "px", default: 20 },
  { key: "duration", label: "Длительность", type: "slider", min: 300, max: 4000, step: 50, unit: "ms", default: 1500 },
  { key: "loopDelay", label: "Пауза", type: "slider", min: 0, max: 3000, step: 50, unit: "ms", default: 900 },
  { key: "bandRatio", label: "Ширина блика", type: "slider", min: 0.1, max: 1, step: 0.05, default: 0.7 },
  { key: "sweep1", label: "Блик 1", type: "color", default: "#c679c4" },
  { key: "sweep2", label: "Блик 2", type: "color", default: "#fa3d1d" },
  { key: "sweep3", label: "Блик 3", type: "color", default: "#ffb005" },
  { key: "sweep4", label: "Блик 4", type: "color", default: "#e1e1fe" },
  { key: "sweep5", label: "Блик 5", type: "color", default: "#0358f7" },
  { key: "loop", label: "Повтор", type: "toggle", default: true },
] as const;

export default function DiaTextScreen() {
  const custom = useCustomize(CUSTOMIZE);
  const words = useMemo(
    () => [custom.word1, custom.word2, custom.word3],
    [custom.word1, custom.word2, custom.word3],
  );
  const sweepColors = useMemo(
    () => [custom.sweep1, custom.sweep2, custom.sweep3, custom.sweep4, custom.sweep5],
    [custom.sweep1, custom.sweep2, custom.sweep3, custom.sweep4, custom.sweep5],
  );
  const textStyle = useMemo(
    () => [styles.word, { color: custom.baseColor, fontSize: custom.fontSize }],
    [custom.baseColor, custom.fontSize],
  );
  const wordWidth = useSharedValue(0);

  const onWordLayout = useCallback(
    (e: LayoutChangeEvent) => {
      const next = e.nativeEvent.layout.width;
      if (Math.abs(next - wordWidth.value) < 0.5) return;
      wordWidth.value =
        wordWidth.value > 0 ? withTiming(next, { duration: 300 }) : next;
    },
    [wordWidth],
  );

  const wordSlotStyle = useAnimatedStyle(() =>
    wordWidth.value > 0 ? { width: wordWidth.value } : {},
  );

  const word = (
    <DiaText
      loop={custom.loop}
      loopDelay={custom.loopDelay}
      duration={custom.duration}
      text={words}
      sweepColors={sweepColors}
      baseColor={custom.baseColor}
      bandRatio={custom.bandRatio}
      textStyle={textStyle}
    />
  );

  return (
    <Showcase>
      <View style={styles.center}>
        <Animated.View style={styles.headline} layout={LinearTransition}>
          <Text style={textStyle}>{custom.lead}</Text>
          {isWeb ? (
            <Animated.View style={[styles.wordSlot, wordSlotStyle]}>
              <View onLayout={onWordLayout}>{word}</View>
            </Animated.View>
          ) : (
            word
          )}
        </Animated.View>
      </View>
    </Showcase>
  );
}

const styles = StyleSheet.create({
  center: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
    paddingHorizontal: 24,
  },
  headline: {
    alignItems: "center",
    gap: 6,
    flexDirection: "row",
    flexWrap: "wrap",
    justifyContent: "center",
    maxWidth: "100%",
  },
  wordSlot: {
    flexDirection: "row",
  },
  word: {
    color: "#f6f3ec",
    fontSize: 20,
    fontWeight: "300",
    letterSpacing: -0.5,
  },
});

Пропсы

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

Тип string | readonly string[]

sweepColors?

Тип readonly string[]

baseColor?

Тип string

duration?

Тип number

delay?

Тип number

loop?

Тип boolean

loopDelay?

Тип number

bandRatio?

Тип number

autoPlay?

Тип boolean

textStyle?

Тип StyleProp<TextStyle>

style?

Тип StyleProp<ViewStyle>

onSweepEnd?

Тип (index: number) => void