Компоненты/Сфера

Сфера

21 августа 2026

Экспериментальная сфера на Skia с анимированной турбулентностью

Установка

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

Файлы

index.tsx
НазадРаскладное менюДалееИсчезающее поле

Вручную

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

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

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

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

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

import React, { useMemo, useRef, useState } from "react";
import { Pressable, StyleSheet, View } from "react-native";
import { StatusBar } from "expo-status-bar";
import { UnstableOrb } from "@/shared/components/organisms/unstable_orb";
import { Showcase, useAutoplay, useCustomize } from "~/showcase";
const colors = ["#1fc7ed", "#4D89FF", "#03629c"];

const CUSTOMIZE = [
  { key: "color1", label: "Цвет 1", type: "color", default: "#1fc7ed" },
  { key: "color2", label: "Цвет 2", type: "color", default: "#4d89ff" },
  { key: "color3", label: "Цвет 3", type: "color", default: "#03629c" },
  { key: "background", label: "Фон", type: "color", default: "#0a0a0a" },
  { key: "colorShift", label: "Сдвиг оттенка", type: "slider", min: 0, max: 360, step: 1, unit: "°", default: 10 },
  { key: "rest", label: "Покой", type: "slider", min: 0, max: 3, step: 0.1, default: 0 },
  { key: "active", label: "Возбуждение", type: "slider", min: 0, max: 5, step: 0.1, default: 2 },
  { key: "size", label: "Размер", type: "slider", min: 120, max: 400, step: 10, unit: "px", default: 250 },
  { key: "hold", label: "Длительность", type: "slider", min: 500, max: 6000, step: 100, unit: "ms", default: 2200 },
] as const;

export default function HomeScreen() {
  const custom = useCustomize(CUSTOMIZE);
  const [excited, setExcited] = useState<boolean>(false);
  const holdRef = useRef(custom.hold);
  holdRef.current = custom.hold;
  const intensity = excited ? custom.active : custom.rest;
  const { color1, color2, color3 } = custom;
  const orbColors = useMemo(
    () =>
      [color1, color2, color3].map((hex, index) =>
        hex.toLowerCase() === colors[index].toLowerCase() ? colors[index] : hex,
      ) as [string, string, string],
    [color1, color2, color3],
  );

  useAutoplay(async ({ wait }) => {
    await wait(700);
    setExcited(true);
    await wait(holdRef.current);
    setExcited(false);
    await wait(900);
  });
  return (
    <Showcase>
      <View style={[styles.container, { backgroundColor: custom.background }]}>
        <StatusBar style="light" />

        <Pressable
          style={styles.orbWrapper}
          onPress={() => setExcited(!excited)}
        >
          <UnstableOrb
            intensity={intensity}
            colorShift={custom.colorShift}
            background={custom.background}
            style={{
              width: 550,
              height: custom.size,
              pointerEvents: "none",
              position: "absolute",
            }}
            colors={orbColors}
          />
        </Pressable>
      </View>
    </Showcase>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#0a0a0a",
    overflow: "hidden",
    justifyContent: "center",
    alignItems: "center",
  },

  orbWrapper: {
    width: 400,
    height: 300,
    justifyContent: "center",
    alignItems: "center",
  },
});

Пропсы

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

Тип number

intensity?

Тип number

background?

Тип string

colors?

Тип [string, string, string]

style?

Тип StyleProp<ViewStyle>