Компоненты/Лучи

Лучи

28 сентября 2026

Насыщенные световые ленты наискосок по чёрному фону, разбитые шумовой маской в зерно.

Установка

npx astra-rn add beams
pnpm dlx astra-rn add beams
yarn dlx astra-rn add beams
bunx --bun astra-rn add beams

Файлы

index.tsx
НазадАврораДалееКаустика

Семь размытых световых лент лежат наискосок на чёрном фоне и медленно дрейфуют и дышат, каждая в своём ритме. Шумовая маска разбивает свет в плотное зерно, а виньетка уводит края в темноту. Ленты рисуются шейдером Skia на GPU.

Когда вы ведёте пальцем или курсором по фону, вся группа лучей плавно наклоняется и смещается за ним. Время идёт на UI-потоке Reanimated; анимация замирает при «Уменьшении движения» и когда приложение свёрнуто. Работает на iOS, Android и в вебе.

Вручную

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

npm install @shopify/react-native-skia react-native-gesture-handler react-native-reanimated
pnpm add @shopify/react-native-skia react-native-gesture-handler react-native-reanimated
yarn add @shopify/react-native-skia react-native-gesture-handler react-native-reanimated
bun add @shopify/react-native-skia react-native-gesture-handler react-native-reanimated

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

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

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

import React, { useMemo } from "react";
import { StyleSheet, View } from "react-native";

import { Beams } from "@/shared/components/organisms/beams";
import { Showcase, useCustomize } from "~/showcase";

const DEEP = "#0b7a96";
const MAIN = "#66d9e8";
const LIGHT = "#c9f6fb";
const MID = "#1fa9c2";
const PALE = "#a5eef6";

const CUSTOMIZE = [
  { key: "angle", label: "Угол лучей", type: "slider", min: -90, max: 90, step: 1, unit: "°", default: -38 },
  { key: "speed", label: "Скорость дрейфа", type: "slider", min: 0.2, max: 4, step: 0.1, unit: "×", default: 1 },
  { key: "blur", label: "Размытие", type: "slider", min: 0, max: 24, step: 1, unit: "px", default: 6 },
  { key: "grain", label: "Зерно", type: "toggle", default: true },
  { key: "deep", label: "Глубокий цвет", type: "color", default: DEEP },
  { key: "main", label: "Основной цвет", type: "color", default: MAIN },
  { key: "mid", label: "Средний цвет", type: "color", default: MID },
  { key: "light", label: "Светлый цвет", type: "color", default: LIGHT },
] as const;

export default function BeamsScreen() {
  const custom = useCustomize(CUSTOMIZE);
  const colors = useMemo(() => {
    const changed = [custom.deep, custom.main, custom.mid, custom.light].some(
      (hex, i) => hex.toLowerCase() !== [DEEP, MAIN, MID, LIGHT][i],
    );
    const pale = custom.light.toLowerCase() === LIGHT ? PALE : custom.light;
    return changed ? [custom.deep, custom.main, custom.light, custom.mid, custom.main, custom.deep, pale] : undefined;
  }, [custom.deep, custom.main, custom.mid, custom.light]);

  return (
    <Showcase>
      <View style={styles.container}>
        <Beams angle={custom.angle} speed={custom.speed} blur={custom.blur} grain={custom.grain} colors={colors} />
      </View>
    </Showcase>
  );
}

const styles = StyleSheet.create({
  container: { ...StyleSheet.absoluteFillObject, backgroundColor: "#000000" },
});

Компонент заполняет родителя — положите его фоном под контент.

Пропсы

Пропс
Описание
angle?

Тип number

colors?

Тип readonly string[]

speed?

Тип number

blur?

Тип number

grain?

Тип boolean

interactive?

Тип boolean

style?

Тип StyleProp<ViewStyle>