Skip to content

Instantly share code, notes, and snippets.

@hungtrn75
Last active May 1, 2025 15:00
Show Gist options
  • Save hungtrn75/1f6be41793980d07018f3d01974c17ba to your computer and use it in GitHub Desktop.
Save hungtrn75/1f6be41793980d07018f3d01974c17ba to your computer and use it in GitHub Desktop.
react-native-counter-numer
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