Last active
May 1, 2025 15:00
-
-
Save hungtrn75/1f6be41793980d07018f3d01974c17ba to your computer and use it in GitHub Desktop.
react-native-counter-numer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, {useCallback, useMemo, useRef} from 'react'; | |
import {StyleSheet, Text, View} from 'react-native'; | |
import Animated, { | |
configureReanimatedLogger, | |
ReanimatedLogLevel, | |
useSharedValue, | |
withDelay, | |
withSpring, | |
} from 'react-native-reanimated'; | |
configureReanimatedLogger({ | |
level: ReanimatedLogLevel.warn, | |
strict: false, // Reanimated runs in strict mode by default | |
}); | |
// Animation configuration constants | |
const SPRING_CONFIG = { | |
damping: 15, | |
stiffness: 150, | |
}; | |
const OPACITY_SPRING_CONFIG = { | |
damping: 20, | |
stiffness: 200, | |
}; | |
const DELAY_TIME = 50; // ms | |
const MAX_TRANSLATE_Y = 20; // pixels | |
const AnimatedText = Animated.createAnimatedComponent(Text); | |
const AnimatedNumberCounter = ({number = 0}) => { | |
// Convert number to array of digits | |
const prevNumber = useRef<number | string>(); | |
const incomingGreaterThanPreviousValue = useSharedValue<{[key: number]: boolean}>({}); | |
const handleNumber = (current: number | string) => { | |
const str = typeof current === 'number' ? current.toString() || '' : current; | |
const splitted = str.split(''); | |
if (prevNumber.current !== current) { | |
const prevStr = typeof prevNumber.current === 'number' ? prevNumber.current.toString() || '' : prevNumber.current; | |
const prevSplitted = (prevStr ?? '').split(''); | |
prevSplitted.forEach((prevChar, index) => { | |
if (prevChar !== splitted[index]) { | |
incomingGreaterThanPreviousValue.value = { | |
...incomingGreaterThanPreviousValue.value, | |
[index]: splitted[index] > prevChar, | |
}; | |
} | |
}); | |
prevNumber.current = current; | |
} | |
return splitted; | |
}; | |
const currentNumber = useMemo(() => handleNumber(number), [number]); | |
// Function to check if the new digit is greater than the previous one | |
const isIncomingGreaterThanPreviousValuePos = useCallback((charPos: number) => { | |
'worklet'; | |
// This would need actual implementation based on your specific logic | |
// For demonstration, let's assume we're tracking the previous digit value separately | |
return incomingGreaterThanPreviousValue.value?.[charPos] ?? true; // Replace with your actual comparison logic | |
}, []); | |
// Animation for when a digit enters the counter | |
const enteringAnimation = useCallback( | |
(charPos = 0) => | |
() => { | |
'worklet'; | |
const isGreaterThanPreviousValue = isIncomingGreaterThanPreviousValuePos(charPos); | |
const animations = { | |
opacity: withSpring(1, OPACITY_SPRING_CONFIG), | |
transform: [ | |
{ | |
translateY: withDelay(DELAY_TIME * charPos, withSpring(0, SPRING_CONFIG)), | |
}, | |
], | |
}; | |
const initialValues = { | |
opacity: 0, | |
transform: [ | |
{ | |
translateY: isGreaterThanPreviousValue ? -MAX_TRANSLATE_Y : MAX_TRANSLATE_Y, | |
}, | |
], | |
}; | |
return {animations, initialValues}; | |
}, | |
[], | |
); | |
// Animation for when a digit exits the counter | |
const exitingAnimation = useCallback( | |
(charPos = 0) => | |
() => { | |
'worklet'; | |
const isGreaterThanPreviousValue = isIncomingGreaterThanPreviousValuePos(charPos); | |
const animations = { | |
opacity: withSpring(0, OPACITY_SPRING_CONFIG), | |
transform: [ | |
{ | |
translateY: withDelay( | |
DELAY_TIME * charPos, | |
withSpring(isGreaterThanPreviousValue ? MAX_TRANSLATE_Y : -MAX_TRANSLATE_Y, SPRING_CONFIG), | |
), | |
}, | |
], | |
}; | |
const initialValues = { | |
opacity: 1, | |
transform: [{translateY: 0}], | |
}; | |
return {animations, initialValues}; | |
}, | |
[], | |
); | |
const renderDigit = useCallback( | |
(digit: string, index: number) => { | |
return ( | |
<AnimatedText | |
key={`${digit}-${index}`} | |
style={styles.digit} | |
entering={enteringAnimation(index)} | |
exiting={exitingAnimation(index)}> | |
{digit} | |
</AnimatedText> | |
); | |
}, | |
[currentNumber], | |
); | |
return ( | |
<View style={styles.container}> | |
<View style={styles.row}>{currentNumber.map(renderDigit)}</View> | |
</View> | |
); | |
}; | |
const styles = StyleSheet.create({ | |
container: { | |
justifyContent: 'center', | |
alignItems: 'center', | |
}, | |
row: { | |
flexDirection: 'row', | |
alignItems: 'center', | |
}, | |
digit: { | |
fontSize: 48, | |
fontWeight: 'bold', | |
fontFamily: 'monospace', // Using monospace for fixed-width digits | |
minWidth: 32, // Ensures consistent width for all digits | |
textAlign: 'center', | |
}, | |
}); | |
export default AnimatedNumberCounter; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment