Konva裁剪图层中Transformer与画布(Stage)坐标不匹配问题
问题根源
你遇到的问题本质是:裁剪图层(Layer)设置了x: clipX和y: clipY的全局偏移,但选择逻辑和Transformer的坐标计算直接使用了Stage的全局坐标,没有转换为图层的局部坐标,导致Transformer的定位和图形变换都基于图层原点(而非Stage原点)出现偏移。
解决方案
核心是把所有从Stage获取的鼠标坐标,转换为当前裁剪图层的局部坐标,再用于选择框和元素交互的逻辑。具体修改点如下:
- 使用
layer.globalToLocal()方法,将Stage全局坐标转换成图层局部坐标 - 选择框是图层内元素,必须用图层局部坐标设置其位置,不能直接用Stage坐标
- 确保选择框的范围和元素的范围都基于同一坐标系(图层局部)
修改后的完整代码
const Workspace: React.FC = () => { const containerRef = useRef<HTMLDivElement>(null); useEffect(() => { const width = window.innerWidth; const height = window.innerHeight; const stage = new Konva.Stage({ container: containerRef.current!, width: width, id: 'stage', height: height, }); const clipX = (width - 500) / 2; const clipY = (height - 500) / 2; const canvasHeight = 500; const canvasWidth = 500; stage.container().style.backgroundColor = '#ededed'; const layer = new Konva.Layer({ x: clipX, y: clipY, clipX: 0, clipY: 0, clipWidth: canvasWidth, clipHeight: canvasHeight, }); stage.add(layer); const canvas = new Konva.Rect({ id: 'canvas', fill: 'white', height: canvasHeight, width: canvasWidth, listening: false, }); layer.add(canvas); const transformer = new Konva.Transformer({ id: 'transformer', }); layer.add(transformer); const rect1 = new Konva.Rect({ x: 60, y: 60, width: 50, height: 50, fill: 'royalblue', name: 'rect', id: 'rect1', draggable: true, }); layer.add(rect1); const text = new Konva.Text({ id: 'text', y: 300, x: 300, fontSize: 20, text: 'Test', fill: '#a43', }); layer.add(text); // Selection logic const selectionRectangle = new Konva.Rect({ fill: 'rgba(41, 171, 241, 0.5)', visible: false, listening: false, }); layer.add(selectionRectangle); let x1: number, y1: number, x2: number, y2: number, selecting = false; stage.on('mousedown touchstart', (e) => { if (!['canvas', 'stage'].includes(e.target.attrs.id)) return; e.evt.preventDefault(); // 将Stage坐标转换为图层局部坐标 const localPos = layer.globalToLocal(stage.getPointerPosition()!); x1 = localPos.x; y1 = localPos.y; x2 = localPos.x; y2 = localPos.y; selectionRectangle.width(0); selectionRectangle.height(0); selecting = true; }); stage.on('mousemove touchmove', (e) => { if (!selecting) return; e.evt.preventDefault(); // 转换坐标到图层局部 const localPos = layer.globalToLocal(stage.getPointerPosition()!); x2 = localPos.x; y2 = localPos.y; selectionRectangle.setAttrs({ visible: true, x: Math.min(x1, x2), y: Math.min(y1, y2), width: Math.abs(x2 - x1), height: Math.abs(y2 - y1), }); }); stage.on('mouseup touchend', (e) => { selecting = false; if (!selectionRectangle.visible()) return; e.evt.preventDefault(); selectionRectangle.visible(false); const shapes = stage.find('Text, .rect'); // 使用图层局部坐标的选择框范围 const box = selectionRectangle.getClientRect(); const selected = shapes.filter((shape) => Konva.Util.haveIntersection(box, shape.getClientRect()), ); transformer.nodes(selected); }); stage.on('click tap', (e) => { if (selectionRectangle.visible()) return; if (e.target === stage) { transformer.nodes([]); return; } if (!e.target.hasName('rect') && e.target.attrs.id !== 'text') return; const metaPressed = e.evt.shiftKey || e.evt.ctrlKey || e.evt.metaKey; const isSelected = transformer.nodes().indexOf(e.target) >= 0; if (!metaPressed && !isSelected) { transformer.nodes([e.target]); } else if (metaPressed && isSelected) { const nodes = transformer.nodes().slice(); nodes.splice(nodes.indexOf(e.target), 1); transformer.nodes(nodes); } else if (metaPressed && !isSelected) { const nodes = transformer.nodes().concat([e.target]); transformer.nodes(nodes); } }); return () => { stage.destroy(); }; }, []); return <div ref={containerRef} />; };
关键修改说明
- 在
mousedown和mousemove事件中,新增layer.globalToLocal()转换鼠标坐标,确保选择框的位置基于图层局部坐标系 - 调整了元素选择的范围,同时包含Text和rect元素(原代码只选了Text,漏掉了rect)
- 点击事件中补充了对text元素的选择支持,避免只能选rect的问题
内容的提问来源于stack exchange,提问作者Demi
相关产品推荐
相关产品推荐

