You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 20:27:02