skia shaders
Soft Noise Surface
A subtle organic background with configurable grain, amplitude, and reduced motion.
iOSAndroidExpo Go
skiareanimated
Installation
Install the dependencies, then copy the files for your preferred renderer.
pnpm add @shopify/react-native-skia react-native-reanimatedInstall tailwind variants
pnpm add tailwind-variantsUsage
SoftNoiseSurfaceExample.tsximport { Text, View } from "react-native";import { SoftNoiseSurface } from "@animations/ui/components/skia-shaders/soft-noise-surface/uniwind";export function SoftNoiseSurfaceExample() { return ( <SoftNoiseSurface classNames={{ content: "px-6 py-8" }} tint="#0ea5e9" > <View> <Text className="text-2xl font-extrabold text-white">Quietly alive</Text> </View> </SoftNoiseSurface> );}soft-noise-surface/config.tsexport const SOFT_NOISE_CONFIG = { amplitude: 0.92, backgroundColor: "#121218", borderRadius: 24, duration: 6_200, frequency: 0.026, intensity: 0.3, octaves: 2, overscan: 40, seed: 8, tint: "#8b7cf6",} as const;export const SOFT_NOISE_GRAYSCALE_MATRIX = [ 0.2126, 0.7152, 0.0722, 0, 0, 0.2126, 0.7152, 0.0722, 0, 0, 0.2126, 0.7152, 0.0722, 0, 0, 0, 0, 0, 1, 0,];soft-noise-surface/noise-layer.tsximport { BlendColor, Canvas, ColorMatrix, Fill, FractalNoise, Image, Rect, useCanvasSize, useClock, useTexture, type SkImage,} from "@shopify/react-native-skia";import { useMemo } from "react";import { StyleSheet } from "react-native";import { useDerivedValue, useReducedMotion, type SharedValue,} from "react-native-reanimated";import { SOFT_NOISE_CONFIG, SOFT_NOISE_GRAYSCALE_MATRIX,} from "./config";import type { SoftNoiseSurfaceBaseProps } from "./types";type NoiseTextureProps = { amplitude: number; animated: boolean; duration: number; frequency: number; height: number; intensity: number; octaves: number; seed: number; tint: string; width: number;};function NoiseTexture({ amplitude, animated, duration, frequency, height, intensity, octaves, seed, tint, width,}: NoiseTextureProps) { const textureWidth = width + SOFT_NOISE_CONFIG.overscan * 2; const textureHeight = height + SOFT_NOISE_CONFIG.overscan * 2; const textureSize = useMemo( () => ({ height: textureHeight, width: textureWidth, }), [textureHeight, textureWidth], ); const texture = useTexture( <Rect height={textureHeight} width={textureWidth} x={0} y={0} > <FractalNoise freqX={frequency} freqY={frequency * 0.82} octaves={octaves} seed={seed} tileHeight={textureHeight} tileWidth={textureWidth} /> <BlendColor color={tint} mode="color"> <ColorMatrix matrix={SOFT_NOISE_GRAYSCALE_MATRIX} /> </BlendColor> </Rect>, textureSize, [ frequency, octaves, seed, textureHeight, textureWidth, tint, ], ); return ( <NoiseTextureImage amplitude={amplitude} animated={animated} duration={duration} height={textureHeight} image={texture} intensity={intensity} width={textureWidth} /> );}type NoiseTextureImageProps = { amplitude: number; animated: boolean; duration: number; height: number; image: SharedValue<SkImage | null>; intensity: number; width: number;};function NoiseTextureImage({ amplitude, animated, duration, height, image, intensity, width,}: NoiseTextureImageProps) { if (animated) { return ( <AnimatedNoiseTextureImage amplitude={amplitude} duration={duration} height={height} image={image} intensity={intensity} width={width} /> ); } return ( <Image height={height} image={image} opacity={Math.min(1, Math.max(0, intensity))} width={width} x={-SOFT_NOISE_CONFIG.overscan} y={-SOFT_NOISE_CONFIG.overscan} /> );}type AnimatedNoiseTextureImageProps = Omit< NoiseTextureImageProps, "animated">;function AnimatedNoiseTextureImage({ amplitude, duration, height, image, intensity, width,}: AnimatedNoiseTextureImageProps) { const clock = useClock(); const x = useDerivedValue(() => { const time = clock.get() / Math.max(1, duration); const drift = Math.sin(time * Math.PI * 2 * 0.71 + 0.8) * 0.58 + Math.sin(time * Math.PI * 2 * 1.37 + 2.1) * 0.27 + Math.sin(time * Math.PI * 2 * 0.19) * 0.15; return ( -SOFT_NOISE_CONFIG.overscan + drift * SOFT_NOISE_CONFIG.overscan * Math.min(1, Math.max(0, amplitude)) ); }); const y = useDerivedValue(() => { const time = clock.get() / Math.max(1, duration); const drift = Math.cos(time * Math.PI * 2 * 0.53 + 1.4) * 0.55 + Math.sin(time * Math.PI * 2 * 1.11 + 0.3) * 0.3 + Math.cos(time * Math.PI * 2 * 0.23 + 2.8) * 0.15; return ( -SOFT_NOISE_CONFIG.overscan + drift * SOFT_NOISE_CONFIG.overscan * Math.min(1, Math.max(0, amplitude)) ); }); return ( <Image height={height} image={image} opacity={Math.min(1, Math.max(0, intensity))} width={width} x={x} y={y} /> );}type SoftNoiseLayerProps = Pick< SoftNoiseSurfaceBaseProps, | "amplitude" | "animated" | "backgroundColor" | "duration" | "frequency" | "intensity" | "octaves" | "paused" | "seed" | "tint">;export function SoftNoiseLayer({ amplitude = SOFT_NOISE_CONFIG.amplitude, animated = true, backgroundColor = SOFT_NOISE_CONFIG.backgroundColor, duration = SOFT_NOISE_CONFIG.duration, frequency = SOFT_NOISE_CONFIG.frequency, intensity = SOFT_NOISE_CONFIG.intensity, octaves = SOFT_NOISE_CONFIG.octaves, paused = false, seed = SOFT_NOISE_CONFIG.seed, tint = SOFT_NOISE_CONFIG.tint,}: SoftNoiseLayerProps) { const { ref, size: { height, width }, } = useCanvasSize(); const reducedMotion = useReducedMotion(); const shouldAnimate = animated && !paused && !reducedMotion; const drawingProps = { amplitude, animated: shouldAnimate, duration, frequency, height, intensity, octaves, seed, tint, width, }; return ( <Canvas pointerEvents="none" ref={ref} style={StyleSheet.absoluteFill} > <Fill color={backgroundColor} /> {width > 0 && height > 0 && ( <NoiseTexture {...drawingProps} /> )} </Canvas> );}soft-noise-surface/types.tsimport type { ReactNode } from "react";export type SoftNoiseSurfaceBaseProps = { accessibilityLabel?: string; amplitude?: number; animated?: boolean; backgroundColor?: string; borderRadius?: number; children: ReactNode; duration?: number; frequency?: number; intensity?: number; octaves?: number; paused?: boolean; seed?: number; tint?: string;};soft-noise-surface/uniwind/index.tsximport { View } from "react-native";import type { SlotsToClasses } from "#shared/types/slots";import { SOFT_NOISE_CONFIG } from "../config";import { SoftNoiseLayer } from "../noise-layer";import type { SoftNoiseSurfaceBaseProps } from "../types";import { softNoiseSurfaceVariants, type SoftNoiseSurfaceSlot,} from "./variants";export type SoftNoiseSurfaceProps = SoftNoiseSurfaceBaseProps & { className?: string; classNames?: SlotsToClasses<SoftNoiseSurfaceSlot>; };export function SoftNoiseSurface({ accessibilityLabel, amplitude, animated, backgroundColor, borderRadius = SOFT_NOISE_CONFIG.borderRadius, children, className, classNames, duration, frequency, intensity, octaves, paused, seed, tint,}: SoftNoiseSurfaceProps) { const { container, content } = softNoiseSurfaceVariants(); return ( <View accessibilityLabel={accessibilityLabel} accessible={accessibilityLabel !== undefined} className={container({ className: [className, classNames?.container], })} style={{ borderRadius }} > <SoftNoiseLayer amplitude={amplitude} animated={animated} backgroundColor={backgroundColor} duration={duration} frequency={frequency} intensity={intensity} octaves={octaves} paused={paused} seed={seed} tint={tint} /> <View className={content({ className: classNames?.content, })} > {children} </View> </View> );}soft-noise-surface/uniwind/variants.tsimport { tv } from "tailwind-variants";export const softNoiseSurfaceSlots = { container: "overflow-hidden rounded-3xl", content: "",} as const;export type SoftNoiseSurfaceSlot = keyof typeof softNoiseSurfaceSlots;export const softNoiseSurfaceVariants = tv({ slots: softNoiseSurfaceSlots,});