this string has no description
heart-anim.tsx
116 lines 3.3 kB view raw
1 2File filter 3import React, { useRef, useState } from 'react' 4import { StyleSheet, Animated, Dimensions } from 'react-native' 5import { Ionicons } from '@expo/vector-icons' 6 7export interface HeartAnimationProps { 8 onComplete?: () => void 9} 10 11export interface HeartAnimationRef { 12 trigger: (x: number, y: number) => void 13} 14 15const getSeed = () => Math.floor(Math.random()) 16 17export const HeartAnimation = React.forwardRef<HeartAnimationRef, HeartAnimationProps>( 18 ({ onComplete }, ref) => { 19 const [showHeart, setShowHeart] = useState(false) 20 const [heartPosition, setHeartPosition] = useState({ x: 0, y: 0 }) 21 const heartScale = useRef(new Animated.Value(0)).current 22 const heartOpacity = useRef(new Animated.Value(0)).current 23 const heartRotation = useRef(new Animated.Value(getSeed())).current 24 25 const triggerHeartAnimation = (x: number, y: number) => { 26 const { width, height } = Dimensions.get('window') 27 28 // Constrain heart position to screen bounds (heart is 80px, so 40px radius) 29 const constrainedX = Math.max(40, Math.min(width - 40, x)) 30 const constrainedY = Math.max(40, Math.min(height - 40, y)) 31 32 setHeartPosition({ x: constrainedX, y: constrainedY }) 33 setShowHeart(true) 34 35 // Generate random rotation between -15 and 15 degrees 36 const randomRotation = (Math.random() - 0.5) * 30 37 heartRotation.setValue(randomRotation) 38 39 Animated.parallel([ 40 Animated.sequence([ 41 Animated.timing(heartScale, { 42 toValue: 1.2, 43 duration: 150, 44 useNativeDriver: true, 45 }), 46 Animated.timing(heartScale, { 47 toValue: 1, 48 duration: 100, 49 useNativeDriver: true, 50 }), 51 ]), 52 Animated.sequence([ 53 Animated.timing(heartOpacity, { 54 toValue: 1, 55 duration: 150, 56 useNativeDriver: true, 57 }), 58 Animated.delay(500), 59 Animated.timing(heartOpacity, { 60 toValue: 0, 61 duration: 300, 62 useNativeDriver: true, 63 }), 64 ]), 65 ]).start(() => { 66 setShowHeart(false) 67 heartScale.setValue(0) 68 heartOpacity.setValue(0) 69 heartRotation.setValue(0) 70 onComplete?.() 71 }) 72 } 73 74 React.useImperativeHandle(ref, () => ({ 75 trigger: triggerHeartAnimation, 76 })) 77 78 if (!showHeart) { 79 return null 80 } 81 82 return ( 83 <Animated.View 84 style={[ 85 heartAnimation.style, 86 { 87 left: heartPosition.x - 40, // Center the 80px heart 88 top: heartPosition.y - 40, 89 transform: [ 90 { scale: heartScale }, 91 { rotate: heartRotation.interpolate({ 92 inputRange: [-180, 180], 93 outputRange: ['-180deg', '180deg'] 94 })} 95 ], 96 opacity: heartOpacity, 97 } 98 ]} 99 pointerEvents="none" 100 > 101 <Ionicons name="heart" size={80} color="#ff3040" /> 102 </Animated.View> 103 ) 104 } 105) 106 107HeartAnimation.displayName = 'HeartAnimation' 108 109const heartAnimation = StyleSheet.create({ 110 style: { 111 position: 'absolute', 112 justifyContent: 'center', 113 alignItems: 'center', 114 zIndex: 1000, 115 }, 116})