rolling counter added

This commit is contained in:
Kakabay 2024-12-20 18:23:26 +05:00
parent 77f4ee0c48
commit 9d4178f1de
2 changed files with 310 additions and 48 deletions

View File

@ -1,67 +1,186 @@
'use client';
import { motion } from 'framer-motion';
import { useEffect, useState } from 'react';
import { useCallback, useMemo, useState, useEffect } from 'react';
interface RollingCounterProps {
numberString: string; // A 10-character string, e.g., "05-12-34-56-78"
numberString: string;
}
const RollingCounter: React.FC<RollingCounterProps> = ({ numberString }) => {
const [rollingValues, setRollingValues] = useState<number[]>([]);
const [isStopped, setIsStopped] = useState<boolean[]>([]); // Track stopped numbers
const ROLLS = 2;
const DIGIT_HEIGHT = 104;
const INITIAL_OFFSET = 38;
const EXTRA_NUMBERS_AFTER = 5;
const EXTRA_NUMBERS_BEFORE = 2;
useEffect(() => {
// Parse input string into an array of numbers, removing hyphens
const targetNumbers = numberString
.replace(/-/g, '')
.split('')
.map((char) => parseInt(char, 10));
setRollingValues(targetNumbers);
setIsStopped(new Array(targetNumbers.length).fill(false));
}, [numberString]);
const getNumbers = (targetValue: number, previousValue?: number) => {
const numbers = [];
const getNumbers = () => Array.from({ length: 10 }, (_, i) => i);
if (previousValue === undefined) {
// Initial load
for (let i = 0; i < ROLLS; i++) {
for (let n = 0; n < 10; n++) {
numbers.push(n);
}
}
// Add sequence before target
for (let n = EXTRA_NUMBERS_BEFORE; n > 0; n--) {
numbers.push((targetValue - n + 10) % 10);
}
numbers.push(targetValue);
// Add extra numbers after target
for (let n = 1; n <= EXTRA_NUMBERS_AFTER; n++) {
numbers.push((targetValue + n) % 10);
}
} else {
// Keep the previous sequence
for (let i = 0; i < ROLLS; i++) {
for (let n = 0; n < 10; n++) {
numbers.push(n);
}
}
// Add sequence before previous value
for (let n = EXTRA_NUMBERS_BEFORE; n > 0; n--) {
numbers.push((previousValue - n + 10) % 10);
}
numbers.push(previousValue);
// Add complete rolls between previous and target
for (let i = 0; i < ROLLS; i++) {
for (let n = 0; n < 10; n++) {
numbers.push(n);
}
}
// Add sequence before target
for (let n = EXTRA_NUMBERS_BEFORE; n > 0; n--) {
numbers.push((targetValue - n + 10) % 10);
}
numbers.push(targetValue);
// Add extra numbers after target
for (let n = 1; n <= EXTRA_NUMBERS_AFTER; n++) {
numbers.push((targetValue + n) % 10);
}
}
return numbers;
};
const RollingDigit = ({
targetValue,
index,
onAnimationComplete,
isStopped,
showHyphen,
previousValue,
}: {
targetValue: number;
index: number;
onAnimationComplete: () => void;
isStopped: boolean;
showHyphen: boolean;
previousValue?: number;
}) => {
const numbers = useMemo(
() => getNumbers(targetValue, previousValue),
[targetValue, previousValue],
);
return (
<div className="flex items-center justify-center bg-lightPrimary text-white py-4 px-6 rounded-full">
{rollingValues.map((targetValue, index) => (
<div key={index} className="flex items-center justify-center">
{/* Container to display numbers */}
<div className="flex items-center">
<div className="overflow-hidden h-[180px] w-[77px] relative">
<motion.div
initial={{ y: '0%' }}
animate={{ y: `-${targetValue * 10}%` }}
initial={false} // Don't reset to initial state
animate={{
y: -(numbers.length - EXTRA_NUMBERS_AFTER - 1) * DIGIT_HEIGHT + INITIAL_OFFSET,
}}
transition={{
duration: 0.5 + index * 0.2,
duration: 2,
delay: index * 0.2,
ease: 'easeInOut',
}}
onAnimationComplete={() => {
// Mark this number as stopped
setIsStopped((prev) => {
const newStatus = [...prev];
newStatus[index] = true;
return newStatus;
});
}}
className="absolute top-1/2 -translate-y-1/2 flex flex-col -mt-[52px]">
{getNumbers().map((num) => (
onAnimationComplete={onAnimationComplete}
className="absolute flex flex-col">
{numbers.map((num, i) => (
<div
key={num}
className={`px-4 py-2 numeric-display-1 transition-colors duration-500 ${
isStopped[index] && num === targetValue
? 'text-white' // Stopped number
: 'text-[#B0B1CD]' // Rolling numbers
key={`${index}-${i}`}
className={`h-[${DIGIT_HEIGHT}px] w-[77px] flex items-center justify-center numeric-display-1 transition-colors duration-500 ${
isStopped && num === targetValue ? 'text-white' : 'text-[#B0B1CD]'
}`}>
{num}
</div>
))}
</motion.div>
</div>
{/* Add a hyphen every two digits */}
{(index + 1) % 2 === 0 && index !== rollingValues.length - 1 && (
<div className="w-[32px] h-[4px] bg-lightOnPrimary mx-5"></div>
)}
{showHyphen && <div className="w-[32px] h-[4px] bg-lightOnPrimary mx-5 self-center" />}
</div>
);
};
const RollingCounter: React.FC<RollingCounterProps> = ({ numberString }) => {
const [isInitialLoading, setIsInitialLoading] = useState(true);
const [isStopped, setIsStopped] = useState<boolean[]>([]);
const [previousNumbers, setPreviousNumbers] = useState<number[]>([]);
const [isNewNumber, setIsNewNumber] = useState(false);
useEffect(() => {
if (!isInitialLoading) {
setPreviousNumbers(numbers);
setIsStopped(new Array(numberString.replace(/-/g, '').length).fill(false));
setIsNewNumber(true);
}
}, [numberString, isInitialLoading]);
const numbers = useMemo(() => {
if (!numberString) return [];
const parsed = numberString
.replace(/-/g, '')
.split('')
.map((char) => parseInt(char, 10));
if (isInitialLoading) {
setIsStopped(new Array(parsed.length).fill(false));
setIsInitialLoading(false);
}
return parsed;
}, [numberString, isInitialLoading]);
const handleAnimationComplete = useCallback((index: number) => {
setIsStopped((prev) => {
const newState = [...prev];
newState[index] = true;
return newState;
});
}, []);
if (isInitialLoading) {
return (
<div className="flex items-center justify-center bg-lightPrimary text-white py-4 px-6 rounded-full">
Loading...
</div>
);
}
return (
<div className="flex items-center justify-center bg-lightPrimary text-white py-4 px-6 rounded-full">
{numbers.map((num, index) => (
<RollingDigit
key={`${index}-${num}`}
targetValue={num}
previousValue={previousNumbers[index]}
index={index}
onAnimationComplete={() => handleAnimationComplete(index)}
isStopped={isStopped[index]}
showHyphen={(index + 1) % 2 === 0 && index !== numbers.length - 1}
/>
))}
</div>
);

View File

@ -0,0 +1,143 @@
'use client';
import { motion } from 'framer-motion';
import { useCallback, useMemo, useState } from 'react';
interface RollingCounterWorkingProps {
numberString: string;
}
// Move constants outside component
const ROLLS = 5;
const DIGIT_HEIGHT = 104;
const INITIAL_OFFSET = 38;
const EXTRA_NUMBERS_AFTER = 5;
const EXTRA_NUMBERS_BEFORE = 2;
// Memoize number generation function
const getNumbers = (targetValue: number) => {
const numbers = [];
// Add complete rolls
for (let i = 0; i < ROLLS; i++) {
for (let n = 0; n < 10; n++) {
numbers.push(n);
}
}
// Add sequence before target
for (let n = EXTRA_NUMBERS_BEFORE; n > 0; n--) {
numbers.push((targetValue - n + 10) % 10);
}
// Add target
numbers.push(targetValue);
// Add extra numbers after target
for (let n = 1; n <= EXTRA_NUMBERS_AFTER; n++) {
numbers.push((targetValue + n) % 10);
}
return numbers;
};
const RollingDigit = ({
targetValue,
index,
onAnimationComplete,
isStopped,
showHyphen,
}: {
targetValue: number;
index: number;
onAnimationComplete: () => void;
isStopped: boolean;
showHyphen: boolean;
}) => {
const numbers = useMemo(() => getNumbers(targetValue), [targetValue]);
const targetIndex = ROLLS * 10 + EXTRA_NUMBERS_BEFORE;
return (
<div className="flex items-center">
<div className="overflow-hidden h-[180px] w-[77px] relative">
<motion.div
initial={{ y: INITIAL_OFFSET }}
animate={{
y: -(targetIndex * DIGIT_HEIGHT) + INITIAL_OFFSET,
}}
transition={{
duration: 5,
delay: index * 4,
ease: 'easeInOut',
}}
onAnimationComplete={onAnimationComplete}
className="absolute flex flex-col">
{numbers.map((num, i) => (
<div
key={`${index}-${i}`}
className={`h-[${DIGIT_HEIGHT}px] w-[77px] flex items-center justify-center numeric-display-1 transition-colors duration-500 ${
isStopped && num === targetValue ? 'text-white' : 'text-[#B0B1CD]'
}`}>
{num}
</div>
))}
</motion.div>
</div>
{showHyphen && <div className="w-[32px] h-[4px] bg-lightOnPrimary mx-5 self-center" />}
</div>
);
};
const RollingCounterWorking: React.FC<RollingCounterWorkingProps> = ({ numberString }) => {
const [isInitialLoading, setIsInitialLoading] = useState(true);
const [isStopped, setIsStopped] = useState<boolean[]>([]);
const numbers = useMemo(() => {
if (!numberString) return [];
const parsed = numberString
.replace(/-/g, '')
.split('')
.map((char) => parseInt(char, 10));
if (isInitialLoading) {
setIsStopped(new Array(parsed.length).fill(false));
setIsInitialLoading(false);
}
return parsed;
}, [numberString, isInitialLoading]);
const handleAnimationComplete = useCallback((index: number) => {
setIsStopped((prev) => {
const newState = [...prev];
newState[index] = true;
return newState;
});
}, []);
if (isInitialLoading) {
return (
<div className="flex items-center justify-center bg-lightPrimary text-white py-4 px-6 rounded-full">
Loading...
</div>
);
}
return (
<div className="flex items-center justify-center bg-lightPrimary text-white py-4 px-6 rounded-full">
{numbers.map((num, index) => (
<RollingDigit
key={`${index}-${num}`}
targetValue={num}
index={index}
onAnimationComplete={() => handleAnimationComplete(index)}
isStopped={isStopped[index]}
showHyphen={(index + 1) % 2 === 0 && index !== numbers.length - 1}
/>
))}
</div>
);
};
export default RollingCounterWorking;