在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ć
相关产品推荐
相关产品推荐

