Unstable Orb
Unstable Orb built with Skia's shader
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/unstable_orb
import React, { memo, useMemo } from "react";import { StyleSheet } from "react-native";import { Canvas, Fill, Skia, Shader, type SkRuntimeEffect as RuntimeEffect, type Uniforms,} from "@shopify/react-native-skia";import { useSharedValue, useFrameCallback, useDerivedValue, withTiming,} from "react-native-reanimated";import { hexToRgb } from "./helper";import type { UnstableOrbProps } from "./types";import { SHADER_SOURCE } from "./conf";export const UnstableOrb: React.FC<UnstableOrbProps> & React.FunctionComponent<UnstableOrbProps> = memo<UnstableOrbProps>( ({ colorShift = 0, intensity = 0.2, background = "#000000", colors = ["#9C43FE", "#4CC2E9", "#101499"], style = { width: 200, height: 200 }, }: UnstableOrbProps): React.ReactNode & React.JSX.Element => { const size = useSharedValue({ width: 0, height: 0 }); const time = useSharedValue<number>(0); const animatedIntensity = useSharedValue<number>(intensity); const shader: RuntimeEffect | null = useMemo(() => { const effect = Skia.RuntimeEffect.Make(SHADER_SOURCE); if (!effect) { console.error("[MetallicPaint] Failed to create runtime effect"); return null; } return effect; }, []); useFrameCallback(() => { time.value += 0.016; }); React.useEffect(() => { animatedIntensity.value = withTiming<number>(intensity, { duration: 400, }); }, [intensity]); const [bgR, bgG, bgB] = hexToRgb<string>(background); const [orb1R, orb1G, orb1B] = hexToRgb<string>(colors[0]); const [orb2R, orb2G, orb2B] = hexToRgb<string>(colors[1]); const [orb3R, orb3G, orb3B] = hexToRgb<string>(colors[2]); const uniforms = useDerivedValue<Uniforms>(() => ({ iTime: time.value, iResolution: [size.value.width, size.value.height], colorShift: colorShift, distortion: animatedIntensity.value, bgColor: [bgR, bgG, bgB], orbColor1: [orb1R, orb1G, orb1B], orbColor2: [orb2R, orb2G, orb2B], orbColor3: [orb3R, orb3G, orb3B], })); return ( <Canvas style={[styles.canvas, style]} onSize={size}> <Fill> <Shader source={shader as RuntimeEffect} uniforms={uniforms} /> </Fill> </Canvas> ); },);const styles = StyleSheet.create({ canvas: { flex: 1, },});export default memo<UnstableOrbProps>(UnstableOrb);Usage
import React, { useState } from "react";import { Button, StyleSheet, View } from "react-native";import { StatusBar } from "expo-status-bar";import { UnstableOrb } from "@/components/organisms/unstable_orb";export default function HomeScreen() { const [intensity, setIntensity] = useState<number>(0.6); return ( <View style={styles.container}> <StatusBar style="light" /> <View style={styles.orbWrapper}> <UnstableOrb intensity={intensity} colorShift={0.15} style={{ width: 220, height: 220 }} /> </View> <View style={{ position: "absolute", bottom: 400 }}> <Button title={intensity > 0 ? "Stop Thinking" : "Start Thinking"} onPress={() => { setIntensity(intensity > 0 ? 0 : 2); }} /> </View> </View> );}const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#000", justifyContent: "center", alignItems: "center", }, orbWrapper: { borderRadius: 28, padding: 16, bottom: 300, overflow: "hidden", // barely-there depth (optional) shadowColor: "#000", shadowOpacity: 0.2, shadowRadius: 24, shadowOffset: { width: 0, height: 12 }, },});Props
React Native Reanimated
React Native Skia
