Initial commit
This commit is contained in:
150
src/components/AnimatedIcon.tsx
Normal file
150
src/components/AnimatedIcon.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
"use client"
|
||||
|
||||
import React, { useEffect, useState, useCallback, useMemo } from "react"
|
||||
import { type LayoutChangeEvent, View } from "react-native"
|
||||
import Animated, {
|
||||
useSharedValue,
|
||||
useAnimatedStyle,
|
||||
withRepeat,
|
||||
withTiming,
|
||||
Easing,
|
||||
withSequence,
|
||||
withDelay,
|
||||
} from "react-native-reanimated"
|
||||
import Auto from "svg/Auto"
|
||||
import Avia from "svg/Avia"
|
||||
|
||||
type Props = {
|
||||
type: "auto" | "avia"
|
||||
}
|
||||
|
||||
const AnimatedIcon = ({ type }: Props) => {
|
||||
const translateX = useSharedValue(0)
|
||||
const translateY = useSharedValue(0)
|
||||
const rotateY = useSharedValue(0)
|
||||
const direction = useSharedValue(1)
|
||||
|
||||
const [containerWidth, setContainerWidth] = useState(0)
|
||||
const iconSize = 40
|
||||
|
||||
const onLayout = useCallback((event: LayoutChangeEvent) => {
|
||||
const { width } = event.nativeEvent.layout
|
||||
setContainerWidth(width)
|
||||
}, []);
|
||||
|
||||
const animationConfig = useMemo(() => ({
|
||||
duration: 4000,
|
||||
easing: Easing.linear,
|
||||
rotationDuration: 300,
|
||||
arcHeight: -30,
|
||||
}), []);
|
||||
|
||||
const createXAnimation = useCallback((maxX: number) => {
|
||||
return withRepeat(
|
||||
withSequence(
|
||||
withTiming(maxX, {
|
||||
duration: animationConfig.duration,
|
||||
easing: animationConfig.easing
|
||||
}, () => {
|
||||
direction.value = -1;
|
||||
rotateY.value = withTiming(180, { duration: animationConfig.rotationDuration });
|
||||
}),
|
||||
withTiming(0, {
|
||||
duration: animationConfig.duration,
|
||||
easing: animationConfig.easing
|
||||
}, () => {
|
||||
direction.value = 1;
|
||||
rotateY.value = withTiming(0, { duration: animationConfig.rotationDuration });
|
||||
})
|
||||
),
|
||||
-1
|
||||
);
|
||||
}, [animationConfig, direction, rotateY]);
|
||||
|
||||
const createYAnimation = useCallback(() => {
|
||||
if (type === "avia") {
|
||||
return withRepeat(
|
||||
withSequence(
|
||||
withTiming(animationConfig.arcHeight, {
|
||||
duration: animationConfig.duration / 2,
|
||||
easing: Easing.out(Easing.quad),
|
||||
}),
|
||||
withTiming(0, {
|
||||
duration: animationConfig.duration / 2,
|
||||
easing: Easing.in(Easing.quad),
|
||||
}),
|
||||
withTiming(animationConfig.arcHeight, {
|
||||
duration: animationConfig.duration / 2,
|
||||
easing: Easing.out(Easing.quad),
|
||||
}),
|
||||
withTiming(0, {
|
||||
duration: animationConfig.duration / 2,
|
||||
easing: Easing.in(Easing.quad),
|
||||
})
|
||||
),
|
||||
-1
|
||||
);
|
||||
}
|
||||
return withTiming(0, { duration: 100 });
|
||||
}, [type, animationConfig]);
|
||||
|
||||
useEffect(() => {
|
||||
if (containerWidth === 0) return;
|
||||
|
||||
const maxX = containerWidth - iconSize;
|
||||
|
||||
translateX.value = createXAnimation(maxX);
|
||||
translateY.value = createYAnimation();
|
||||
}, [containerWidth, type, createXAnimation, createYAnimation, translateX, translateY]);
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
transform: [
|
||||
{ translateX: translateX.value },
|
||||
{ translateY: translateY.value },
|
||||
{ rotateY: `${rotateY.value}deg` },
|
||||
],
|
||||
}
|
||||
});
|
||||
|
||||
const containerStyle = useMemo(() => ({
|
||||
height: 100,
|
||||
justifyContent: "center" as const,
|
||||
backgroundColor: "transparent" as const,
|
||||
position: "relative" as const,
|
||||
}), []);
|
||||
|
||||
const trackStyle = useMemo(() => ({
|
||||
height: 2,
|
||||
backgroundColor: "#28A7E850",
|
||||
position: "absolute" as const,
|
||||
top: 25,
|
||||
left: 0,
|
||||
right: 0,
|
||||
}), []);
|
||||
|
||||
const iconContainerStyle = useMemo(() => ({
|
||||
position: "absolute" as const,
|
||||
top: 0,
|
||||
height: iconSize,
|
||||
width: iconSize,
|
||||
}), [iconSize]);
|
||||
|
||||
const renderIcon = useMemo(() => {
|
||||
if (type === "auto") {
|
||||
return <Auto color="#28A7E8" width={iconSize} height={iconSize} />;
|
||||
}
|
||||
return <Avia color="#28A7E8" width={iconSize} height={iconSize} />;
|
||||
}, [type, iconSize]);
|
||||
|
||||
return (
|
||||
<View onLayout={onLayout} style={containerStyle}>
|
||||
<View style={trackStyle} />
|
||||
<Animated.View style={[iconContainerStyle, animatedStyle]}>
|
||||
{renderIcon}
|
||||
</Animated.View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default AnimatedIcon;
|
||||
Reference in New Issue
Block a user