LotteryReel.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import React, { useEffect, useRef } from 'react';
  2. import { Animated, Easing, StyleSheet, View } from 'react-native';
  3. import TransCard from './TransCard';
  4. interface LotteryReelProps {
  5. pool: any[];
  6. result: any;
  7. width: number;
  8. itemWidth: number;
  9. duration?: number;
  10. index?: number;
  11. delay?: number;
  12. }
  13. export default function LotteryReel({
  14. pool,
  15. result,
  16. width,
  17. itemWidth,
  18. duration = 3000,
  19. index = 0,
  20. delay = 0,
  21. }: LotteryReelProps) {
  22. const slideAnim = useRef(new Animated.Value(0)).current;
  23. // ... (buildReelData remains same) ...
  24. const buildReelData = () => {
  25. // Guard
  26. if (!pool || pool.length === 0) {
  27. // Log fallback
  28. console.log(`Reel ${index}: Empty pool, using fallback`);
  29. const fallback = result ? [result, result, result, result, result] : [];
  30. return {
  31. reelData: fallback.map((item, idx) => ({ ...item, uniqueKey: `fb-${idx}` })),
  32. targetOffset: 0
  33. };
  34. }
  35. let basePool = [...pool];
  36. // Ensure base pool is large enough
  37. if (basePool.length > 0) {
  38. while (basePool.length < 10) {
  39. basePool = [...basePool, ...basePool];
  40. }
  41. }
  42. // Shuffle and expand
  43. // Use consistent seed or just math random
  44. const shuffle = (arr: any[]) => [...arr].sort(() => Math.random() - 0.5);
  45. let array = [...shuffle(basePool)];
  46. if (basePool.length > 0) {
  47. while (array.length < 25) { // Reduced from 50 to 25 to minimize cumulative render error
  48. array = [...array, ...shuffle(basePool)];
  49. }
  50. }
  51. // Set target
  52. const activeIndex = array.length - 5; // Target is closer to end
  53. // Inject result with winner flag
  54. array[activeIndex] = { ...result, key: `res-${index}`, isWinner: true };
  55. const reelData = array.map((item, idx) => ({ ...item, uniqueKey: `${idx}-${item.id || item.goodsId || idx}` }));
  56. // Calculate target offset
  57. // TranslateX moves LEFT, so negative.
  58. // We want the center of item at activeIndex to be at center of View (width/2)
  59. // Item center = activeIndex * itemWidth + itemWidth/2
  60. // Offset = width/2 - Item center
  61. const targetOffset = width / 2 - (activeIndex * itemWidth + itemWidth / 2);
  62. console.log(`Reel ${index} (Final): poolLength=${pool.length}, reelLength=${reelData.length}, activeIndex=${activeIndex}, target=${targetOffset}`);
  63. return { reelData, targetOffset };
  64. };
  65. const { reelData, targetOffset } = React.useMemo(() => buildReelData(), [pool, result, width]);
  66. useEffect(() => {
  67. if (targetOffset !== 0) {
  68. // Reset to 0 first
  69. slideAnim.setValue(0);
  70. // Use requestAnimationFrame to ensure render is complete before animating
  71. // helping with the "stuck" feel during initial heavy mount
  72. requestAnimationFrame(() => {
  73. Animated.sequence([
  74. Animated.delay(delay),
  75. Animated.timing(slideAnim, {
  76. toValue: targetOffset,
  77. duration: duration,
  78. easing: Easing.out(Easing.cubic),
  79. useNativeDriver: true,
  80. })
  81. ]).start();
  82. });
  83. }
  84. }, [targetOffset, delay]);
  85. return (
  86. <View style={[styles.container, { width }]}>
  87. <Animated.View style={[styles.reel, { transform: [{ translateX: slideAnim }] }]}>
  88. {reelData.map((item, idx) => (
  89. <View key={item.uniqueKey} style={[styles.itemWrapper, { width: itemWidth }]}>
  90. <TransCard
  91. item={item}
  92. width={itemWidth === 94 ? 90 : itemWidth - 4}
  93. height={itemWidth === 94 ? 121 : 90}
  94. fill={itemWidth === 94 ? 2 : 2}
  95. imageWidth={itemWidth === 94 ? 74 : 55}
  96. imageHeight={itemWidth === 94 ? 94 : 70}
  97. />
  98. </View>
  99. ))}
  100. </Animated.View>
  101. </View>
  102. );
  103. }
  104. const styles = StyleSheet.create({
  105. container: {
  106. overflow: 'hidden',
  107. },
  108. reel: {
  109. flexDirection: 'row',
  110. },
  111. itemWrapper: {
  112. justifyContent: 'center',
  113. alignItems: 'center',
  114. },
  115. });