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

在react-konva中如何阻止拖拽形状角点引发的旋转及非法变换?

解决React-Konva矩形变换的旋转问题与控制点重叠限制

一、阻止矩形拖拽角点时的180°旋转

默认情况下,Konva的Transformer允许拖拽角点翻转形状,触发±180°旋转。可以通过两种方式解决:

1. 直接禁用旋转功能

如果不需要旋转操作,直接隐藏旋转手柄,从根源避免旋转问题:

import { Transformer } from 'react-konva';

// 在组件中使用Transformer时添加配置
<Transformer
  node={rectRef.current}
  rotateEnabled={false} // 完全禁用旋转手柄
/>

2. 实时约束旋转角度(保留旋转但禁止翻转)

如果需要保留旋转功能,但要避免±180°翻转导致的错位,可通过onTransform事件实时修正角度:

import { useState, useRef } from 'react';
import { Rect, Transformer, Stage, Layer } from 'react-konva';

function ShapeEditor() {
  const rectRef = useRef();
  const [rectProps, setRectProps] = useState({
    x: 50,
    y: 50,
    width: 100,
    height: 100,
    fill: 'red',
    rotation: 0
  });

  const handleTransform = () => {
    const node = rectRef.current;
    const transform = node.getTransform();
    let rotation = transform.rotation();

    // 将角度限制在-179°~179°区间,避免触发180°翻转
    if (Math.abs(rotation) >= 180) {
      rotation = rotation > 0 ? rotation - 360 : rotation + 360;
      node.setRotation(rotation);
      node.getLayer().batchDraw();
    }

    // 更新组件状态,保持位置尺寸与变换同步
    setRectProps({
      ...rectProps,
      x: transform.x(),
      y: transform.y(),
      width: transform.width(),
      height: transform.height(),
      rotation: rotation
    });
  };

  return (
    <Stage width={window.innerWidth} height={window.innerHeight}>
      <Layer>
        <Rect ref={rectRef} {...rectProps} />
        <Transformer node={rectRef.current} onTransform={handleTransform} />
      </Layer>
    </Stage>
  );
}

export default ShapeEditor;

二、禁用形状变换+限制控制点不重叠到其他形状

1. 完全禁用形状变换

如果不需要任何变换操作,直接给Transformer添加enabled={false}:

<Transformer
  node={rectRef.current}
  enabled={false} // 完全禁用所有变换操作
/>

2. 允许变换但禁止控制点重叠

若需要保留变换功能,但阻止当前形状的控制点拖拽到其他形状的控制点上,可在onTransformEnd事件中检查重叠并回滚:

import { useState, useRef } from 'react';
import { Rect, Transformer, Stage, Layer } from 'react-konva';

function MultiShapeEditor() {
  const [shapes, setShapes] = useState([
    { id: 1, x: 50, y: 50, width: 100, height: 100, fill: 'red', rotation: 0 },
    { id: 2, x: 200, y: 200, width: 100, height: 100, fill: 'blue', rotation: 0 }
  ]);
  const shapeRefs = useRef({});
  const [selectedId, setSelectedId] = useState(null);

  const handleShapeClick = (id) => setSelectedId(id);

  // 检查当前形状的控制点是否与其他形状重叠
  const checkOverlap = (currentNode) => {
    const currentBounds = currentNode.getClientRect();
    const currentPoints = [
      { x: currentBounds.x, y: currentBounds.y },
      { x: currentBounds.x + currentBounds.width, y: currentBounds.y },
      { x: currentBounds.x, y: currentBounds.y + currentBounds.height },
      { x: currentBounds.x + currentBounds.width, y: currentBounds.y + currentBounds.height }
    ];

    for (const shape of shapes) {
      if (shape.id === selectedId) continue;
      const otherNode = shapeRefs.current[shape.id];
      const otherBounds = otherNode.getClientRect();
      const otherPoints = [
        { x: otherBounds.x, y: otherBounds.y },
        { x: otherBounds.x + otherBounds.width, y: otherBounds.y },
        { x: otherBounds.x, y: otherBounds.y + otherBounds.height },
        { x: otherBounds.x + otherBounds.width, y: otherBounds.y + otherBounds.height }
      ];

      // 设置5px误差范围,避免精度问题导致误判
      for (const p1 of currentPoints) {
        for (const p2 of otherPoints) {
          if (Math.abs(p1.x - p2.x) < 5 && Math.abs(p1.y - p2.y) < 5) {
            return true;
          }
        }
      }
    }
    return false;
  };

  const handleTransformEnd = () => {
    const currentNode = shapeRefs.current[selectedId];
    const originalProps = shapes.find(s => s.id === selectedId);

    if (checkOverlap(currentNode)) {
      // 重叠则回滚到变换前状态
      currentNode.setAttrs(originalProps);
      currentNode.getLayer().batchDraw();
    } else {
      // 无重叠则更新状态
      const transform = currentNode.getTransform();
      setShapes(shapes.map(s => 
        s.id === selectedId 
          ? { ...s, x: transform.x(), y: transform.y(), width: transform.width(), height: transform.height(), rotation: transform.rotation() }
          : s
      ));
    }
  };

  return (
    <Stage width={window.innerWidth} height={window.innerHeight}>
      <Layer>
        {shapes.map(shape => (
          <Rect
            key={shape.id}
            ref={node => shapeRefs.current[shape.id] = node}
            {...shape}
            onClick={() => handleShapeClick(shape.id)}
          />
        ))}
        {selectedId && (
          <Transformer
            node={shapeRefs.current[selectedId]}
            onTransformEnd={handleTransformEnd}
          />
        )}
      </Layer>
    </Stage>
  );
}

export default MultiShapeEditor;

内容的提问来源于stack exchange,提问作者Vejsil Hrustić

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 21:05:19