Grainy Gradient
A Skia-powered animated gradient with a subtle grain texture
Last updated on
Manual
Install the following dependencies:
npm install react-native-reanimated @shopify/react-native-skiaCopy and paste the following code into your project.
component/organisms/grainy-gradient.tsx
// @ts-checkimport React, { memo, useMemo } from "react";import { Canvas, Skia, Fill, Shader, type SkRuntimeEffect,} from "@shopify/react-native-skia";import { useSharedValue, useDerivedValue, useFrameCallback, type FrameInfo,} from "react-native-reanimated";import { GRAINY_GRADIENT_SHADER } from "./conf";import { hexToRgba } from "./helper";import { useWindowDimensions } from "react-native";import type { IGrainyGradient } from "./types";export const GrainyGradient: React.FC<IGrainyGradient> & React.FunctionComponent<IGrainyGradient> = memo< React.ComponentProps<typeof GrainyGradient> | IGrainyGradient>( ({ width: paramsWidth, height: paramsHeight, colors = ["#5b0bb5", "#7c3aed", "#fb923c", "#db2777"], speed = 2.9, animated = true, intensity = 0.112, size = 1.9, enabled = true, amplitude = 0.1, brightness = 0, style, }: React.ComponentProps<typeof GrainyGradient> | IGrainyGradient): | (React.ReactElement & React.ReactNode & React.JSX.Element) | null => { const { width: screenWidth, height: screenHeight } = useWindowDimensions(); const width = paramsWidth ?? screenWidth; const height = paramsHeight ?? screenHeight; const shader = useMemo<SkRuntimeEffect | null>( () => Skia.RuntimeEffect.Make(GRAINY_GRADIENT_SHADER), [], ); const progress = useSharedValue<number>(0); useFrameCallback((info: FrameInfo) => { if (animated && info.timeSincePreviousFrame) { progress.value += (info.timeSincePreviousFrame / 1000) * speed; } }, animated); const parsedColors = useMemo(() => { const result: [number, number, number, number][] = []; for (let i = 0; i < 5; i++) { result.push(i < colors.length ? hexToRgba(colors[i]!) : [0, 0, 0, 1]); } return result; }, [colors]); const uniforms = useDerivedValue(() => ({ iResolution: [width, height], iTime: progress.value, uColor0: parsedColors[0], uColor1: parsedColors[1], uColor2: parsedColors[2], uColor3: parsedColors[3], uColor4: parsedColors[4], uColorCount: Math.min(colors?.length, 5), uAmplitude: amplitude, uGrainIntensity: intensity, uGrainSize: size, uGrainEnabled: enabled ? 1 : 0, uBrightness: brightness, })); if (!shader) return null; return ( <Canvas style={[{ width, height }, style]}> <Fill> <Shader source={shader} uniforms={uniforms} /> </Fill> </Canvas> ); },);export default memo< React.FC<IGrainyGradient> & React.FunctionComponent<IGrainyGradient>>(GrainyGradient);Usage
import React from "react";import { StyleSheet, View } from "react-native";import { GrainyGradient } from "@/components/organisms/grainy-gradient";export default function App(): React.ReactNode & React.JSX.Element & React.ReactElement { return ( <View style={stylez.container}> <GrainyGradient /> </View> );}const stylez = StyleSheet.create({ container: { flex: 1, justifyContent: "center", alignItems: "center", },});Props
React Native Reanimated
React Native Skia
