CSS Transition导致transform:rotate追踪鼠标时跳变的解决方法
解决箭头跟随鼠标旋转的角度跳变问题
我正在用JavaScript/React给静态页面添加动画交互,遇到一个棘手的问题:箭头组件跟随鼠标旋转时,用CSS transition做平滑过渡会出现突兀的跳变。原因是三角函数计算的角度会从225deg直接跳到-135deg(由三角函数的取值范围导致),导致CSS过渡走了最长的旋转路径。
现有代码
HTML结构
<section className='home' ref={ref}> <div className='intro'> <div className='image-section' ref={positionRef}> <div className='spinny-thing' style={{transform: `rotate(${angle}deg)`}} ></div> </div> </div> </section>
React逻辑
import useMousePosition from '@react-hook/mouse-position' // 鼠标位置监听(来自@react-hook/mouse-position包) const [mousePostion, ref] = useMousePosition(0, 0, 30) const positionRef = React.useRef() // 箭头容器的中心坐标 const [xPosition, setXPosition] = React.useState(null) const [yPosition, setYPosition] = React.useState(null) const getPosition = () => { const x = positionRef.current.offsetLeft setXPosition(x + (positionRef.current.offsetWidth / 2)) const y = positionRef.current.offsetTop setYPosition(y + (positionRef.current.offsetHeight / 2)) } React.useEffect(() => { getPosition() }, []) React.useEffect(() => { window.addEventListener("resize", getPosition) }, []) const [angle, setAngle] = React.useState(0) React.useEffect(() => { // 计算鼠标相对于容器中心的角度,+45是为了让箭头初始朝向正确 let mouse_angle = (Math.atan2(mousePostion.pageY - yPosition, mousePostion.pageX - xPosition)) * (180 / Math.PI ) + 45 setAngle(mouse_angle) }, [mousePostion])
CSS样式
.spinny-thing { width: 300px; height: 300px; background-color: orange; border-radius: 50% 15px 50% 50%; transition: all .1s ease; }
试过的方案及困境
- 用if语句判断角度正负调整数值,但只是把跳变转移到了其他位置;
- 去掉CSS的
0.1s过渡能解决跳变,但旋转完全失去平滑效果。
需要一个既能解决角度正负切换时的跳变,又能保留平滑过渡的方案。
解决方案
核心思路是让角度始终保持连续的数值变化,避免出现跨360度的突变。推荐以下两种方法:
方法1:将角度转换为0-360的正数值
把计算出的负角度转换为0-360范围内的正角度,确保数值连续:
React.useEffect(() => { if (xPosition === null || yPosition === null) return let mouse_angle = (Math.atan2(mousePostion.pageY - yPosition, mousePostion.pageX - xPosition)) * (180 / Math.PI ) + 45 // 将角度转换为0-360之间的正数 mouse_angle = (mouse_angle + 360) % 360 setAngle(mouse_angle) }, [mousePostion, xPosition, yPosition])
注意:需要在依赖中加入
xPosition和yPosition,确保容器位置变化时角度能重新计算。
方法2:基于上一次角度计算最短旋转路径(更可靠)
记录上一次的角度,每次计算新角度时选择最短的旋转路径,避免跳变:
const [angle, setAngle] = React.useState(0) // 用ref记录上一次的角度,避免状态更新延迟 const prevAngleRef = React.useRef(0) React.useEffect(() => { if (xPosition === null || yPosition === null) return // 计算原始角度(范围-180~180) const rawAngle = (Math.atan2(mousePostion.pageY - yPosition, mousePostion.pageX - xPosition)) * (180 / Math.PI ) + 45 // 计算与上一次角度的差值 let delta = rawAngle - prevAngleRef.current // 调整差值到-180~180之间,选择最短旋转路径 delta = ((delta + 180) % 360) - 180 // 计算新的连续角度 const newAngle = prevAngleRef.current + delta setAngle(newAngle) prevAngleRef.current = newAngle }, [mousePostion, xPosition, yPosition])
这种方法会始终让箭头沿着最短的方向旋转,不管鼠标怎么移动,角度都会连续变化,完美解决跳变问题,同时保留CSS的平滑过渡效果。
内容的提问来源于stack exchange,提问作者marshy
相关产品推荐
相关产品推荐

