Компоненты/Волнистый бегунок

Волнистый бегунок

21 февраля 2026

Бегунок на Skia с текущей синусоидой

Установка

npx astra-rn add squiggly-slider
pnpm dlx astra-rn add squiggly-slider
yarn dlx astra-rn add squiggly-slider
bunx --bun astra-rn add squiggly-slider

Файлы

index.tsx
НазадДве секцииДалееСквиркл

Вручную

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

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

Скопируйте эти файлы в src/shared/components/molecules/squiggly-slider/:

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

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

import { SquigglySlider } from "@/shared/components/molecules/squiggly-slider";
import React, { useEffect, useState } from "react";
import { useWindowDimensions } from "react-native";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { Showcase, hexToRgb, useAutoplay, useCustomize } from "~/showcase";

const CUSTOMIZE = [
  { key: "activeColor", label: "Цвет заполнения", type: "color", default: "#ffffff" },
  { key: "inactiveColor", label: "Цвет трека", type: "color", default: "#ffffff" },
  { key: "inactiveOpacity", label: "Яркость трека", type: "slider", min: 0, max: 1, step: 0.05, default: 0.3 },
  { key: "thumbColor", label: "Цвет ползунка", type: "color", default: "#ffffff" },
  { key: "amplitude", label: "Амплитуда", type: "slider", min: 0, max: 20, step: 0.5, unit: "px", default: 4 },
  { key: "wavelength", label: "Длина волны", type: "slider", min: 20, max: 200, step: 1, unit: "px", default: 60 },
  { key: "strokeWidth", label: "Толщина", type: "slider", min: 1, max: 16, step: 0.5, unit: "px", default: 6 },
  { key: "speed", label: "Скорость волны", type: "slider", min: 0, max: 4, step: 0.1, default: 1 },
  { key: "progress", label: "Автопрогресс", type: "toggle", default: true },
] as const;

export default () => {
  const [v, setV] = useState<number>(0.35);
  const [flattened, setFlattened] = useState<boolean>(false);
  const { width } = useWindowDimensions();
  const custom = useCustomize(CUSTOMIZE);
  const inactiveColor = (() => {
    if (custom.inactiveColor === "#ffffff" && custom.inactiveOpacity === 0.3) return "rgba(255, 255, 255, 0.3)";
    const { r, g, b } = hexToRgb(custom.inactiveColor);
    return `rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, ${custom.inactiveOpacity})`;
  })();
  useEffect(() => {
    if (!custom.progress) return;
    const interval = setInterval(() => {
      setV((prev) => (prev >= 1 ? 0 : prev + 0.001));
    }, 100);
    return () => clearInterval(interval);
  }, [custom.progress]);

  const { playing } = useAutoplay(
    async ({ wait }) => {
      const glide = async (from: number, to: number, ms: number) => {
        const steps = Math.round(ms / 40);
        for (let i = 1; i <= steps; i++) {
          const t = i / steps;
          setV(from + (to - from) * (t * t * (3 - 2 * t)));
          await wait(40);
        }
      };
      setFlattened(false);
      setV(0.35);
      await wait(1200);
      setFlattened(true);
      await glide(0.35, 0.7, 700);
      await wait(150);
      await glide(0.7, 0.31, 800);
      await wait(150);
      setFlattened(false);
      await wait(1600);
    },
  );

  useEffect(() => {
    if (!playing) setFlattened(false);
  }, [playing]);

  return (
    <Showcase>
      <GestureHandlerRootView
        style={{
          flex: 1,

          justifyContent: "center",
          alignItems: "center",
        }}
      >
        <SquigglySlider
          value={v}
          onValueChange={setV}
          width={width}
          flattened={flattened}
          thumbColor={custom.thumbColor === "#ffffff" ? "#fff" : custom.thumbColor}
          activeColor={custom.activeColor === "#ffffff" ? "#fff" : custom.activeColor}
          inactiveColor={inactiveColor}
          amplitude={custom.amplitude}
          wavelength={custom.wavelength === 60 ? undefined : custom.wavelength}
          strokeWidth={custom.strokeWidth}
          speed={custom.speed}
        />
      </GestureHandlerRootView>
    </Showcase>
  );
};

ISquigglySlider

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

Тип number

onValueChange

Тип <T extends number>(value: T) => void

onSlidingComplete?

Тип () => void

width

Тип number

height?

Тип number

strokeWidth?

Тип number

wavelength?

Тип number

amplitude?

Тип number

activeColor?

Тип string

inactiveColor?

Тип string

thumbColor?

Тип string

speed?

Тип number

flattened?

Тип boolean