Chroma Ring
An animated chroma ring border with flowing glow
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/chroma-ring
import React, { memo, useEffect } from "react";import { View, StyleSheet } from "react-native";import { Canvas, Shader, Skia, Fill, Uniforms,} from "@shopify/react-native-skia";import { useDerivedValue, useSharedValue, withRepeat, withTiming, Easing,} from "react-native-reanimated";import { type IChromaRing } from "./types";import { SHADER_SOURCE } from "./conf";import { hexToRgb } from "./helper";const LIQUID_METAL_BORDER_SHADER = Skia.RuntimeEffect.Make(SHADER_SOURCE)!;export const ChromaRing: React.FC<IChromaRing> = memo<IChromaRing>( ({ width = 300, height = 56, borderRadius: customBorderRadius, borderWidth = 2, speed = 1.0, base = "#333340", glow = "#c0c8e0", background = "#0a0a0a", children, style, }) => { const borderRadius = customBorderRadius ?? height / 2; const baseColorRgb = hexToRgb<typeof base>(base); const glowColorRgb = hexToRgb<typeof glow>(glow); const time = useSharedValue<number>(0); useEffect(() => { time.value = withRepeat( withTiming(Math.PI * 200, { duration: 200000, easing: Easing.linear, }), -1, false, ); }, [time]); const uniforms = useDerivedValue<Uniforms>(() => ({ iResolution: [width, height], iTime: time.value, borderWidth: borderWidth, borderRadius: borderRadius, speed: speed, baseColor: baseColorRgb, glowColor: glowColorRgb, })); return ( <View style={[styles.container, { width, height, borderRadius }, style]}> <Canvas style={[StyleSheet.absoluteFill, { borderRadius }]}> <Fill> <Shader source={LIQUID_METAL_BORDER_SHADER} uniforms={uniforms} /> </Fill> </Canvas> <View style={[ styles.innerBackground, { backgroundColor: background, borderRadius: Math.max(0, borderRadius - borderWidth), margin: borderWidth, }, ]} /> <View style={[styles.contentContainer, { borderRadius }]}> {children} </View> </View> ); },);const styles = StyleSheet.create({ container: { position: "relative", overflow: "hidden", }, innerBackground: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, }, contentContainer: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, justifyContent: "center", alignItems: "center", overflow: "hidden", },});export default memo<React.FC<IChromaRing>>(ChromaRing);Usage
import { StyleSheet, Image, View, Text } from "react-native";import { GestureHandlerRootView } from "react-native-gesture-handler";import { StatusBar } from "expo-status-bar";import { MatchedGeometry } from "@/components/organisms/matched-geometry";import { useFonts } from "expo-font";import { ChromaRing } from "@/components/organisms/chroma-ring";import { SymbolView } from "expo-symbols";export default function App() { const [fontLoaded] = useFonts({ SfProRounded: require("@/assets/fonts/sf-pro-rounded.ttf"), HelveticaNowDisplay: require("@/assets/fonts/HelveticaNowDisplayMedium.ttf"), }); return ( <GestureHandlerRootView style={styles.container}> <StatusBar style="light" /> <View style={styles.stage}> <ChromaRing glow="#000000" base="#000000"> <View style={{ flexDirection: "row", alignItems: "center", gap: 8, padding: 12, }} > <SymbolView name="ring.dashed" tintColor={"#fff"} size={20} /> <Text style={[ styles.title, fontLoaded && { fontFamily: "HelveticaNowDisplay" }, ]} > Chroma Ring </Text> </View> </ChromaRing> </View> </GestureHandlerRootView> );}const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#000", alignItems: "center", }, stage: { paddingTop: 150, }, card: { width: 200, borderRadius: 18, backgroundColor: "rgba(255,255,255,0.06)", overflow: "hidden", bottom: 200, right: 100, }, image: { width: "100%", height: 200, borderRadius: 18, }, meta: { padding: 12, }, title: { color: "#fff", fontSize: 15, }, subtitle: { color: "rgba(255,255,255,0.6)", fontSize: 12, },});Props
React Native Reanimated
React Native Skia
