携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情
konva绘制基础矩形
我们需要传入 (x, y) 设置矩形的左上角,并用 width 和 height 设置其的宽高。
import { Rect } from 'react-konva';
<Rect
x={20}
y={50}
width={100}
height={100}
fill="red"
shadowBlur={10}
/>
通过设置draggable允许矩形可拖拽,监听onDragMove,onDragEnd,onDragStart等拖拽事件,处理相关逻辑。
<Rect
onDblClick={onSelect} // 双击选中
onClick={onSelect}
onTap={onSelect}
onDragStart={(e) => {
onDragStart(e);
}}
onTransformStart={(e) => {
}}
draggable
onDragMove={(e) => {
onDragMove();
}}
onDragEnd={(e) => {
onDragEnd();
}}
onTransform={(e) => {
console.log('onTransform')
const node = shapeRef.current;
const scaleX = node.scaleX();
const scaleY = node.scaleY();
node.scaleX(1);
node.scaleY(1);
onChange({
...shapeProps,
x: node.x(),
y: node.y(),
// set minimal value
width: Math.max(5, node.width() * scaleX),
height: Math.max(node.height() * scaleY),
});
}}
onTransformEnd={(e) => {
const node = shapeRef.current;
const scaleX = node.scaleX();
const scaleY = node.scaleY();
node.scaleX(1);
node.scaleY(1);
onChange({
...shapeProps,
x: node.x(),
y: node.y(),
// set minimal value
width: Math.max(5, node.width() * scaleX),
height: Math.max(node.height() * scaleY),
});
}}
/>
变形组件Transform
基础用法
<Transformer
ref={trRef}
rotateEnabled={false} // 禁止旋转
boundBoxFunc={(oldBox, newBox) => {
// limit resize
if (newBox.width < 5 || newBox.height < 5) {
return oldBox;
}
return newBox;
}}
/>
配置参数
- rotateEnabled 是否允许旋转(false则没有头部的旋转操作标示)
完整示例
import React from 'react';
import { createRoot } from 'react-dom/client';
import { Stage, Layer, Rect, Transformer } from 'react-konva';
const Rectangle = ({ shapeProps, isSelected, onSelect, onChange }) => {
const shapeRef = React.useRef();
const trRef = React.useRef();
React.useEffect(() => {
if (isSelected) {
// we need to attach transformer manually
trRef.current.nodes([shapeRef.current]);
trRef.current.getLayer().batchDraw();
}
}, [isSelected]);
return (
<React.Fragment>
<Rect
onClick={onSelect}
onTap={onSelect}
ref={shapeRef}
{...shapeProps}
draggable
onDragEnd={(e) => {
onChange({
...shapeProps,
x: e.target.x(),
y: e.target.y(),
});
}}
onTransformEnd={(e) => {
// transformer is changing scale of the node
// and NOT its width or height
// but in the store we have only width and height
// to match the data better we will reset scale on transform end
const node = shapeRef.current;
const scaleX = node.scaleX();
const scaleY = node.scaleY();
// we will reset it back
node.scaleX(1);
node.scaleY(1);
onChange({
...shapeProps,
x: node.x(),
y: node.y(),
// set minimal value
width: Math.max(5, node.width() * scaleX),
height: Math.max(node.height() * scaleY),
});
}}
/>
{isSelected && (
<Transformer
ref={trRef}
boundBoxFunc={(oldBox, newBox) => {
// limit resize
if (newBox.width < 5 || newBox.height < 5) {
return oldBox;
}
return newBox;
}}
/>
)}
</React.Fragment>
);
};
const initialRectangles = [
{
x: 10,
y: 10,
width: 100,
height: 100,
fill: 'red',
id: 'rect1',
},
{
x: 150,
y: 150,
width: 100,
height: 100,
fill: 'green',
id: 'rect2',
},
];
const App = () => {
const [rectangles, setRectangles] = React.useState(initialRectangles);
const [selectedId, selectShape] = React.useState(null);
const checkDeselect = (e) => {
const clickedOnEmpty = e.target === e.target.getStage();
if (clickedOnEmpty) {
selectShape(null);
}
};
return (
<Stage
width={window.innerWidth}
height={window.innerHeight}
onMouseDown={checkDeselect}
onTouchStart={checkDeselect}
>
<Layer>
{rectangles.map((rect, i) => {
return (
<Rectangle
key={i}
shapeProps={rect}
isSelected={rect.id === selectedId}
onSelect={() => {
selectShape(rect.id);
}}
onChange={(newAttrs) => {
const rects = rectangles.slice();
rects[i] = newAttrs;
setRectangles(rects);
}}
/>
);
})}
</Layer>
</Stage>
);
};
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);