React转盘二次旋转异常:如何追踪位置并保证至少两圈旋转?
问题分析与解决方案
问题根源
你的转盘二次旋转异常的核心原因是每次旋转使用绝对角度而非累加角度:
- 首次旋转后,转盘停在固定绝对角度,但二次旋转时直接计算新的绝对目标角度。如果新角度小于上次角度,转盘会反向旋转;
- 旋转时长基于
currentSpinCount * 200ms计算,当新的currentSpinCount小于上次值时,总时长变短,视觉上转速变慢; - 未保存上次旋转后的累计位置,无法基于此计算符合要求的正向旋转量。
修复方案
新增两个useRef追踪累计旋转步数和上次中奖元素索引,确保每次旋转都是在上次位置基础上正向累加至少2整圈,再转到目标位置:
- 用
lastSpinCountRef记录累计旋转总步数,lastWinnerIndexRef记录上次中奖元素索引; - 计算从上次位置到目标位置的正向步数,避免反向旋转;
- 始终使用累加的总步数设置旋转角度,保证旋转方向和圈数符合要求。
修改后的完整代码
import { useState, useRef } from 'react' import { Items } from './mocks/items.json' import './App.css' // 计算总权重 const totalOdds = Items.reduce((acc, curr) => acc + curr.odds, 0); const ITEM_COUNT = Items.length; function App() { const [spinning, setSpinning] = useState(false); const [winner, setWinner] = useState(null); const wheelRef = useRef(); // 追踪累计旋转步数和上次中奖元素索引 const lastSpinCountRef = useRef(0); const lastWinnerIndexRef = useRef(0); const spin = () => { setSpinning(true); // 计算中奖元素及其索引 let random = Math.random() * totalOdds; let currentOdds = 0; let currentWinner = null; let targetIndex = 0; for (let i = 0; i < ITEM_COUNT; i++) { currentOdds += Items[i].odds; if (random <= currentOdds) { currentWinner = Items[i]; targetIndex = i; break; } } // 计算从上次位置到目标位置的正向步数,避免反向 const stepsToTarget = (targetIndex + ITEM_COUNT - lastWinnerIndexRef.current) % ITEM_COUNT; // 至少旋转2整圈,再加上到目标位置的步数 const currentSpinCount = 2 * ITEM_COUNT + stepsToTarget; // 更新累计总步数 const totalSpinCount = lastSpinCountRef.current + currentSpinCount; // 设置转盘旋转样式 wheelRef.current.style.transitionDuration = `${currentSpinCount * 200}ms`; wheelRef.current.style.transform = `rotate(-${(360 / ITEM_COUNT) * totalSpinCount}deg)`; // 旋转结束后更新状态和追踪变量 setTimeout(() => { setSpinning(false); setWinner(currentWinner); lastSpinCountRef.current = totalSpinCount; lastWinnerIndexRef.current = targetIndex; }, currentSpinCount * 200); }; return ( <div className="wheel-container"> <div className={`wheel ${spinning ? "spinning" : ""}`} ref={wheelRef} > {Items.map((item, index) => ( <div key={index} className={`${winner && winner.name === item.name ? "winner" : ""}`} style={{ transform: `rotate(${(360 / ITEM_COUNT) * index}deg)`, }} > {item.name} <div className="odds">{(item.odds * 100).toFixed(2)}%</div> </div> ))} </div> <button className="spin-button" onClick={spin} disabled={spinning} > {spinning ? "Spinning..." : "Spin"} </button> {winner && ( <div className="winner"> The winner is <span>{winner.name}</span>! </div> )} </div> ) } export default App
关键修改点说明
lastSpinCountRef:记录从初始位置开始的累计旋转总步数,确保每次旋转基于之前的位置累加;stepsToTarget:通过(targetIndex + ITEM_COUNT - lastWinnerIndexRef.current) % ITEM_COUNT计算正向旋转步数,避免负数导致反向旋转;currentSpinCount:固定包含至少2整圈的步数,保证每次旋转满足"至少转两圈"的要求;- 旋转时长基于本次新增的旋转步数计算,保证转速一致,不会出现变慢的情况。
内容的提问来源于stack exchange,提问作者krst221
相关产品推荐
相关产品推荐

