React Konva实现矩形跟随用户自定义点路径动画的问题求助
问题排查
- 动画核心逻辑错误:原代码直接将
frame.time / 10作为矩形的x、y坐标赋值,完全没有按照点位之间的路径做插值计算,导致移动逻辑和预设路径无关 - 闭包陷阱:
Konva.Animation的回调函数捕获的是useEffect运行时的i和coords值,后续状态更新后回调内拿不到最新值,会导致索引异常 - 索引越界风险:没有判断
i >= coords.length的边界情况,路径点走完后继续访问coords[i]会抛出报错 - 状态冲突:同时通过ref直接修改Rect的x/y属性、又通过
points状态绑定Rect的x/y,两者更新冲突会导致位置跳动 - 移动逻辑错误:原代码分别判断x、y是否到达目标值,会触发两次alert和两次索引+1,单路径点会被重复消费
修正后代码
import { useState, useRef, useEffect } from 'react'; import { Stage, Layer, Group, Rect, Circle } from 'react-konva'; import Konva from 'konva'; // 通用按钮组件,可替换为你项目中的UI组件 const Button = ({ onClick, children }) => ( <button onClick={onClick} style={{position: 'fixed', top: 10, left: 10, padding: '8px 16px', zIndex: 999}} > {children} </button> ); export default function PathAnimation() { const [coords, setCoords] = useState([]); // 存储用户点击生成的路径点位 const [isTracing, setIsTracing] = useState(false); // 动画启动状态标记 const [currentPointIndex, setCurrentPointIndex] = useState(0); // 当前待移动到的目标点索引 const rectRef = useRef(null); const SEGMENT_DURATION = 1000; // 单段路径移动时长,单位ms,可自行调整速度 const handleStart = () => { if (coords.length === 0) return; setIsTracing(prev => !prev); // 停止动画时重置索引,下次启动从头开始,不需要可删除该行 if (isTracing) setCurrentPointIndex(0); }; const handleStageClick = (e) => { if (isTracing) return; const stage = e.target.getStage(); const clickPos = stage.getPointerPosition(); setCoords(prev => [...prev, [clickPos.x, clickPos.y]]); }; useEffect(() => { // 未启动动画或所有路径点已走完时直接返回 if (!isTracing || currentPointIndex >= coords.length) { if (currentPointIndex >= coords.length) setIsTracing(false); return; } const rectNode = rectRef.current; if (!rectNode) return; // 计算当前段的起点和终点 const startPoint = currentPointIndex === 0 ? [rectNode.x(), rectNode.y()] : coords[currentPointIndex - 1]; const targetPoint = coords[currentPointIndex]; const offsetX = targetPoint[0] - startPoint[0]; const offsetY = targetPoint[1] - startPoint[1]; const pathAnim = new Konva.Animation((frame) => { // 计算当前段移动进度,范围0-1 const progress = Math.min(frame.time / SEGMENT_DURATION, 1); // 线性插值计算实时坐标 rectNode.x(startPoint[0] + offsetX * progress); rectNode.y(startPoint[1] + offsetY * progress); // 到达当前目标点,切换下一段 if (progress >= 1) { pathAnim.stop(); setCurrentPointIndex(prev => prev + 1); } }, rectNode.getLayer()); pathAnim.start(); return () => pathAnim.stop(); }, [isTracing, currentPointIndex, coords]); return ( <div> <Stage onMouseDown={handleStageClick} width={window.innerWidth} height={window.innerHeight} > <Layer> <Group> <Rect width={50} height={50} x={250} y={250} strokeWidth={2} fill="black" draggable ref={rectRef} /> </Group> {coords.map((coord, index) => ( <Circle key={index} x={coord[0]} y={coord[1]} radius={4} fill="black" /> ))} </Layer> </Stage> <Button onClick={handleStart}> {isTracing ? '停止追踪' : '开始追踪'} </Button> </div> ); }
核心调整说明
- 移除了冲突的
points状态,统一通过ref操作Rect的位置,避免状态更新和直接操作Konva节点的冲突 - 重构动画逻辑,逐段计算路径移动进度,用线性插值计算实时坐标,移动轨迹完全贴合用户添加的点位
- 新增边界判断,点位为空时不启动动画,所有点位走完后自动停止动画
- 修复闭包问题,每个路径段的动画单独生成,依赖更新后自动重新计算动画参数
- 优化交互逻辑,按钮支持启停切换,可自行调整
SEGMENT_DURATION参数修改移动速度
内容的提问来源于stack exchange,提问作者tanmya
相关产品推荐
相关产品推荐

