Chroma Ring

An animated chroma ring border with flowing glow

Last updated on

Edit on GitHub

Manual

Install the following dependencies:

npm install react-native-reanimated @shopify/react-native-skia

Copy 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