Семь размытых световых лент лежат наискосок на чёрном фоне и медленно дрейфуют и дышат, каждая в своём ритме. Шумовая маска разбивает свет в плотное зерно, а виньетка уводит края в темноту. Ленты рисуются шейдером Skia на GPU.
Когда вы ведёте пальцем или курсором по фону, вся группа лучей плавно наклоняется и смещается за ним. Время идёт на UI-потоке Reanimated; анимация замирает при «Уменьшении движения» и когда приложение свёрнуто. Работает на iOS, Android и в вебе.
Поставьте зависимости:
npm install @shopify/react-native-skia react-native-gesture-handler react-native-reanimatedpnpm add @shopify/react-native-skia react-native-gesture-handler react-native-reanimatedyarn add @shopify/react-native-skia react-native-gesture-handler react-native-reanimatedbun 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>