I'm trying to create an animated gradient for my expo app. I know this can be done for websites using pure CSS:
.container {
background: linear-gradient(45deg, "red", "green", "blue", "yellow")
animation: color 12s ease-in-out infinite
}
But I'm not able to figure out how to do it in React Native. I was originally using expo-linear-gradient for rendering the Linear gradient but apparently that package can't be used for creating animations so I switched to React Native Skia (with Reanimated). I tried for many hours but I still wasn't able to get that effect. And I'm not able to find anything online.
Can someone give a hint on how this could be achieved.
Animations are the one thing react-native differs greatly from react in. I recommend using react-native-reanimated for animations. Going with react native skia for animating gradients seemed to be the right move. I tried to use reanimated's createAnimatedComponent
for expo-linear-gradient and couldn't get to work, so I went with react native skia. Most Skia components accepts SharedValue
s so the animating them is just doing reanimated stuff to your values:
import {
colorManipulators,
} from "@phantom-factotum/colorutils";
import {
Canvas,
LinearGradient,
Rect,
vec
} from '@shopify/react-native-skia';
import { ReactNode, useEffect, useMemo, useState } from 'react';
import { LayoutRectangle, StyleSheet, View, ViewStyle } from 'react-native';
import { interpolateColor, useDerivedValue, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated';
type Props = {
contentContainerStyle?: ViewStyle;
children: ReactNode;
colors: string[];
start:[number,number]
end:[number,number]
// style?:ViewStyle;
};
export default function AnimatedGradient({
contentContainerStyle,
children,
colors,
start,
end
}: Props) {
// store children layout properties
const [layout, setLayout] = useState<LayoutRectangle>({
width: 0,
height: 0,
x: 0,
y: 0,
});
const animValue = useSharedValue(0)
const darkColors = useMemo(()=>colors.map(color=>colorManipulators.blend(color,'black',0.5)),[colors])
const animatedColors = useDerivedValue(()=>{
return colors.map((color,i)=>interpolateColor(animValue.value,[0,1],[color,darkColors[i]]))
})
useEffect(()=>{
animValue.value = withRepeat(
withTiming(1,{duration:500}),
-1,
true
)
},[])
return (
<>
<Canvas
style={{
// Canvas can only have skia elements within it
// so position it absolutely and place non-skia elements
// on top of it
position: 'absolute',
width:layout.width,
height:layout.height
}}>
<Rect x={0} y={0} width={layout.width} height={layout.height} strokeWidth={1}>
<LinearGradient
colors={animatedColors}
origin={vec(layout.width/2, layout.height/2)}
start={vec(layout.width*start[0],layout.height*start[1])}
end={vec(layout.width*end[0],layout.height*end[1])}
/>
</Rect>
</Canvas>
<View
style={styles.contentContainer}
onLayout={(e) => setLayout(e.nativeEvent.layout)}>
{children}
</View>
</>
);
}
const styles = StyleSheet.create({
contentContainer: {
backgroundColor: 'transparent',
},
});