/ components / NessieAnimation.tsx
NessieAnimation.tsx
1 // was used in the index but isn't anymore, not used rn 2 3 import React, { useRef, useEffect, useCallback } from 'react'; 4 import { Animated, Easing, View, StyleSheet, ViewStyle } from 'react-native'; 5 6 const CYCLE_DURATION = 8000; // 8 seconds for the full cycle 7 const MAX_RIGHT_DISTANCE = 80; // Maximum 80px to the right from starting position 8 const MIN_DELAY = 8000; // Minimum 8 seconds delay 9 const MAX_DELAY = 30000; // Maximum 30 seconds delay 10 const MIN_WALK_DISTANCE = 80; 11 const MAX_WALK_DISTANCE = 100; 12 const BACKFLIP_DURATION = 1000; // 1 second for the backflip 13 const BACKFLIP_CHANCE = 0.3; // 30% chance of backflip during delay 14 15 interface NessieAnimationProps { 16 style?: ViewStyle; 17 imageSize?: number; 18 } 19 20 export const NessieAnimation: React.FC<NessieAnimationProps> = ({ 21 style, 22 imageSize = 30, 23 }) => { 24 const progress = useRef(new Animated.Value(0)).current; 25 const backflipRotation = useRef(new Animated.Value(0)).current; 26 const walkDistance = useRef(0); 27 const startPosition = useRef(0); 28 29 const generateNewWalkCycle = useCallback(() => { 30 const newWalkDistance = Math.floor( 31 Math.random() * (MAX_WALK_DISTANCE - MIN_WALK_DISTANCE + 1) + 32 MIN_WALK_DISTANCE 33 ); 34 let newStartPosition = Math.max( 35 0, 36 Math.min(startPosition.current, MAX_RIGHT_DISTANCE - newWalkDistance) 37 ); 38 39 walkDistance.current = newWalkDistance; 40 startPosition.current = newStartPosition; 41 }, []); 42 43 const performBackflip = useCallback(() => { 44 Animated.timing(backflipRotation, { 45 toValue: 1, 46 duration: BACKFLIP_DURATION, 47 easing: Easing.linear, 48 useNativeDriver: true, 49 }).start(() => { 50 backflipRotation.setValue(0); 51 }); 52 }, [backflipRotation]); 53 54 const animateWithDelay = useCallback(() => { 55 const delay = Math.random() * (MAX_DELAY - MIN_DELAY) + MIN_DELAY; 56 57 Animated.sequence([ 58 Animated.delay(delay), 59 Animated.timing(progress, { 60 toValue: 1, 61 duration: CYCLE_DURATION, 62 easing: Easing.linear, 63 useNativeDriver: true, 64 }), 65 ]).start(({ finished }) => { 66 if (finished) { 67 progress.setValue(0); 68 generateNewWalkCycle(); 69 if (Math.random() < BACKFLIP_CHANCE) { 70 performBackflip(); 71 } 72 animateWithDelay(); 73 } 74 }); 75 }, [generateNewWalkCycle, progress, performBackflip]); 76 77 useEffect(() => { 78 generateNewWalkCycle(); 79 animateWithDelay(); 80 81 return () => { 82 progress.stopAnimation(); 83 backflipRotation.stopAnimation(); 84 }; 85 }, [generateNewWalkCycle, animateWithDelay]); 86 87 const translateX = progress.interpolate({ 88 inputRange: [0, 5 / 12, 1 / 2, 11 / 12, 1], 89 outputRange: [ 90 startPosition.current, 91 startPosition.current + walkDistance.current, 92 startPosition.current + walkDistance.current, 93 startPosition.current, 94 startPosition.current, 95 ], 96 }); 97 98 const scaleX = progress.interpolate({ 99 inputRange: [0, 5 / 12, 5 / 12 + 0.001, 11 / 12, 11 / 12 + 0.001, 1], 100 outputRange: [1, 1, -1, -1, 1, 1], 101 }); 102 103 const translateY = progress.interpolate({ 104 inputRange: [ 105 0, 106 1 / 24, 107 1 / 12, 108 1 / 4, 109 5 / 12, 110 1 / 2, 111 17 / 24, 112 3 / 4, 113 11 / 12, 114 1, 115 ], 116 outputRange: [0, -2, 0, -2, 0, 0, -2, 0, -2, 0], 117 extrapolate: 'clamp', 118 }); 119 120 const rotateY = backflipRotation.interpolate({ 121 inputRange: [0, 1], 122 outputRange: ['0deg', '360deg'], 123 }); 124 125 return ( 126 <View style={[styles.container, style]}> 127 <Animated.Image 128 source={require('@/assets/images/nessie.png')} 129 style={[ 130 styles.image, 131 { 132 width: imageSize, 133 height: imageSize, 134 transform: [ 135 { translateX }, 136 { translateY }, 137 { scaleX }, 138 { rotateY }, 139 ], 140 }, 141 ]} 142 /> 143 </View> 144 ); 145 }; 146 147 const styles = StyleSheet.create({ 148 container: { 149 justifyContent: 'center', 150 alignItems: 'center', 151 }, 152 image: { 153 resizeMode: 'contain', 154 }, 155 });