携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情
功能介绍
鼠标框选一段,实现框选可拖拽更改; 框选内容转图片
鼠标框选
主要实现思路:
监听鼠标事件
onMouseDown onMousemove onMouseUp 主要Stage上监听这几个鼠标事件
拿到鼠标坐标
const { x, y } = StageRef.current.getPointerPosition();
绘制方框
这里主要通过设置一个固定的框选Rect,onMouseDown时设置Rect的visible为true,onMouseUp时设置visible为false。并添加rect到Rect列表,重制选中Rect属性
const areaAttr = {
x: Math.min(tranLeft, x2),
y: Math.min(tranTop, y2),
width: Math.abs(x2 - tranLeft),
height: Math.abs(y2 - tranTop),
visible: true,
};
setSelectionRectAttrs(Object.assign({}, selectionRectAttrs, areaAttr));
选中内容转图片
konva的toDataURL 方法即可
const data = ImageRef.current.toDataURL({
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
});
关键代码
// 画布
import React, { useState, useRef, useEffect } from 'react';
import { Stage, Layer, Image, Group } from 'react-konva';
import useImage from 'use-image';
import Rectangle from './Rectangle';
import { Html } from 'react-konva-utils';
const initialRectangles = [
{
x: 0,
y: 0,
width: 150,
height: 100,
stroke: 'blue',
id: 'rect0',
},
{
x: 150,
y: 150,
width: 50,
height: 150,
stroke: 'blue',
id: 'rect1',
},
];
export default function ImageStage({ ref, ImageUrl, height, width, onConfirm }) {
const [image] = useImage(ImageUrl);
const [rectangles, setRectangles] = useState(initialRectangles);
const [selectedId, setSelectedId] = useState(null);
const [selectedRect, setSelectedRect] = useState({
x: 0, y: 0, height: 0, width: 0,
});
const [isMoving, setIsmoving] = useState(false); // 缩放比例
const [isDragging, setIsDragging] = useState(false);
const [conFirmVal, setConFirmVal] = useState('');
const [x, setX] = useState(0);
const [y, setY] = useState(0);
const [tranLeft, setTranLeft] = useState(0);
const [tranTop, setTranTop] = useState(0);
const [selectionRectAttrs, setSelectionRectAttrs] = useState({
x: 250,
y: 250,
width: 50,
height: 50,
stroke: 'blue',
fill: 'rgba(0,0,255,0.5)',
id: 'selectionRect',
visible: false,
});
const ImageRef = useRef();
const StageRef = useRef();
async function handleGetImageUrl(rect = selectedRect) {
setConFirmVal('');
const data = ImageRef.current.toDataURL({
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
});
const result = await putUploadUrl(data);
if(result && result.TextDetections?.length > 0){
let text = result.TextDetections.map(i => {
return i.DetectedText
})
setConFirmVal(text.join(''))
}else{
setConFirmVal('')
}
}
// 当前鼠标是否在框选内,如果在框选内,则不能再添加框选
function canMove(x, y) {
const list = rectangles.filter(rec => (rec.x <= x && x <= rec.x + rec.width) && rec.y <= y && y <= rec.y + rec.height);
return list.length <= 0;
}
function onMouseDown(e) {
const tranLeft = (e.evt.offsetX + x / stageScale);
setTranLeft(tranLeft);
const tranTop = (e.evt.offsetY + y / stageScale);
setTranTop(tranTop);
// TODO:判断是否mousedown选中了其他可拖动的框
setIsmoving(canMove(tranLeft, tranTop));
}
function onMousemove(e) {
if (!isMoving || isDragging) return;
const { x, y } = StageRef.current.getPointerPosition();
const x2 = x / stageScale;
const y2 = y / stageScale;
// todo 框选范围太大取消框选
const areaAttr = {
x: Math.min(tranLeft, x2),
y: Math.min(tranTop, y2),
width: Math.abs(x2 - tranLeft),
height: Math.abs(y2 - tranTop),
visible: true,
};
setSelectionRectAttrs(Object.assign({}, selectionRectAttrs, areaAttr));
}
function onMouseUp(e) {
if (!selectionRectAttrs.visible) {
return;
}
setIsmoving(false);
const id = `'rect' + ${rectangles.length}`;
const newRec = Object.assign({}, selectionRectAttrs, {
id,
fill: 'transparent',
});
setRectangles([...rectangles, newRec]);
setSelectedId(id);
setSelectedRect(newRec);
handleGetImageUrl(newRec);
setSelectionRectAttrs(Object.assign({}, selectionRectAttrs, {
visible: false,
}));
}
function handleScaleBig() {
setStageScale(stageScale + 0.1);
};
function handleScaleSmall() {
setStageScale(stageScale - 0.1);
};
const checkDeselect = (e) => {
console.log('checkDeselect', e);
};
const handleRotation = () => {
const currentRef = StageRef.current;
};
return (
<div className="stage-view" id="stage-view" >
<Stage
ref={StageRef}
onMouseUp={onMouseUp}
onMousemove={onMousemove}
onMouseDown={onMouseDown}
onTouchStart={checkDeselect
width={width}
height={height}
>
{/* 图片底图图层 */}
<Layer
width={width}
height={height}
>
<Image
width={width}
height={height}
ref={ImageRef}
image={image} />
{rectangles.map((rect, i) => (
<Rectangle
key={i}
shapeProps={rect}
isSelected={rect.id === selectedId}
onSelect={() => {
setSelectedRect(rect);
setSelectedId(rect.id);
}}
onDragStart={() => {
setIsDragging(true);
}}
onDragMove={() => {
setSelectedRect(rect);
setSelectedId(rect.id);
}}
onDragEnd={() => {
setSelectedRect(rect);
setSelectedId(rect.id);
setIsDragging(false);
handleGetImageUrl(rect);
}}
onChange={(newAttrs) => {
const rects = rectangles.slice();
rects[i] = newAttrs;
setRectangles(rects);
setSelectedRect(rect);
setSelectedId(rect.id);
}}
/>
))}
{/* 框选rect */}
<Rectangle
shapeProps={selectionRectAttrs}
isSelected={selectionRectAttrs.visible}
onSelect={() => {
setSelectedId(null);
}}
onChange={(newAttrs) => {
console.log('newAttrs', newAttrs); // 当前变更信息
}}
/>
<Group >
</Group>
</Layer>
</Stage>
</div>
);
}