Картинка переводится в ограниченную палитру через пороговую матрицу Байера 2×2, 4×4 или 8×8. Без image дизерится встроенная анимированная сцена со сферой; с image — ваша фотография, крупными пикселями.
Шейдер считается целиком на GPU через Skia, время идёт на UI-потоке Reanimated — React не перерисовывается на каждом кадре. Анимация замирает, когда включено «Уменьшение движения», приложение свёрнуто, передан paused или (в вебе) поверхность ушла за пределы экрана. Работает на iOS, Android и в вебе.
Поставьте зависимости:
npm install @shopify/react-native-skia react-native-reanimatedpnpm add @shopify/react-native-skia react-native-reanimatedyarn add @shopify/react-native-skia react-native-reanimatedbun add @shopify/react-native-skia react-native-reanimatedСкопируйте эти файлы в src/shared/components/organisms/dither/:
Проверьте алиас путей. Импорты в коде выше записаны так, как их пишет astra-rn add: @/shared/components, @/shared/utils и @/shared/hooks. Нужен алиас "@/*": ["./src/*"] в tsconfig.json — или поправьте импорты под свои пути.
import React from "react";
import { StyleSheet, View } from "react-native";
import { Dither } from "@/shared/components/organisms/dither";
import { Showcase, useCustomize } from "~/showcase";
import { PHOTO_SOURCES } from "~/assets/photos";
const CUSTOMIZE = [
{ key: "darkColor", label: "Тёмный", type: "color", default: "#0a0a0a" },
{ key: "lightColor", label: "Светлый", type: "color", default: "#f4f1ea" },
{ key: "pixelSize", label: "Пиксель", type: "slider", min: 1, max: 10, step: 1, default: 3, unit: "px" },
{ key: "levels", label: "Уровни", type: "slider", min: 2, max: 6, step: 1, default: 2 },
{ key: "matrix", label: "Матрица", type: "select", options: [{value: "2", label: "2×2"},{value: "4", label: "4×4"},{value: "8", label: "8×8"}], default: "8" },
{ key: "contrast", label: "Контраст", type: "slider", min: 0.5, max: 2.5, step: 0.05, default: 1.2 },
{ key: "colorMode", label: "Цвет", type: "select", options: [{value: "palette", label: "Палитра"},{value: "source", label: "Исходный"}], default: "palette" },
{ key: "usePhoto", label: "Фото вместо градиента", type: "toggle", default: false },
{ key: "speed", label: "Скорость", type: "slider", min: 0, max: 3, step: 0.05, default: 1 },
] as const;
const PHOTO = PHOTO_SOURCES["photo-stack-1"];
export default function DitherScreen() {
const custom = useCustomize(CUSTOMIZE);
return (
<Showcase>
<View style={styles.container}>
<Dither
fill
darkColor={custom.darkColor}
lightColor={custom.lightColor}
pixelSize={custom.pixelSize}
levels={custom.levels}
contrast={custom.contrast}
speed={custom.speed}
matrix={Number(custom.matrix) as 2 | 4 | 8}
colorMode={custom.colorMode === "source" ? "source" : "palette"}
image={custom.usePhoto ? PHOTO : undefined}
/>
</View>
</Showcase>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#0a0a0a",
},
});По умолчанию компонент занимает ширину родителя и высоту height. Чтобы растянуть его фоном на весь экран, передайте fill.
image?Тип DataSourceParam
darkColor?Тип string
lightColor?Тип string
pixelSize?Тип number
levels?Тип number
matrix?Тип DitherMatrix
contrast?Тип number
colorMode?Тип DitherColorMode
speed?Тип number
paused?Тип boolean
fill?Тип boolean
width?Тип number
height?Тип number
borderRadius?Тип number
children?Тип ReactNode
style?Тип StyleProp<ViewStyle>