前言
有段时间没更新我们的图片编辑器了,最近做了重构和完善了节点的框选功能,重构主要实现了core sdk,方便之后可以React和Vue多个框架实现和兴编辑器。今天我们主要看下节点框选功能,节点框选支持shift+鼠标左键和矩形框选实行多个节点框选。后续会介绍core sdk功能。
演示
代码实现
主要是通过Knova的Transformer中的nodes属性来实现多个节点的移动。
shift+鼠标左键
首先我们在画布上监听click事件,监听鼠标单击和按下shift键的时候选中多个节点。
stage.on('click', (event) => {
if (rectangleVisible()) {
return;
}
// 判断如果不是点的背景画布触发节点多选
if (!isBg(event.target.attrs)) {
// 是否有按下shift键
const metaPressed =
event.evt.shiftKey || event.evt.ctrlKey || event.evt.metaKey;
// 当前节点是否选中
const isSelected = canvas.tr.nodes().indexOf(event.target) >= 0;
// 节点没有选中和没有按下shift键的时候。添加第一个记节点
if (!metaPressed && !isSelected) {
canvas.tr.nodes([event.target]);
} else if (metaPressed && isSelected) { // shifit建按下,并且节点已选中
const nodes = canvas.tr.nodes().slice();
nodes.splice(nodes.indexOf(event.target), 1);
canvas.tr.nodes(nodes);
} else if (metaPressed && !isSelected) { // shifit建按下,并且节点没有选中过,直接添加
const nodes = canvas.tr.nodes().concat([event.target]);
canvas.tr.nodes(nodes);
}
canvas.layer.add(canvas.tr);
} else {
// 点击其它区域,清空节点多选
canvas.tr.nodes([]);
}
});
shift+鼠标左键到这里就可以了,比较简单。下面我们说下框选实现节点的多选
矩形框选
首先实现我们的矩形框,矩形框的实现主要通过三个事件,mousedown,mousemove和mouseup事件,主要原理为mousedown记录鼠标的位置,mousemove通过坐标的变动绘制矩形,mouseup 计算哪些节点在我们的矩形范围内,然后实现选中。下面我们开始来实现
创建一个影藏的矩形,添加到画布当中
selectionRectangle = new Konva.Rect({
fill: 'rgba(0,0,255,0.5)',
visible: false,
});
layer.add(selectionRectangle);
mousedown事件
- 获取鼠标在画布中的位置
const position = stage.getPointerPosition();
- 记录位置
注意:这里要关注下画布的缩放,否则位置会算的不准确
获取到缩放后的坐标
let { x, y } = position;
x = x / scale;
y = y / scale;
记录起始位置和拖动后的位置
// 起始位置
x1 = x;
y1 = y;
// 移动后的位置
x2 = x;
y2 = y;
- 设置矩形可见
selectionRectangle.visible(true);
selectionRectangle.width(0);
selectionRectangle.height(0);
mousemove 事件
- 获取鼠标在画布中的位置并计算出缩放后的位置
const position = stage.getPointerPosition();
x2 = x / scale;
y2 = y / scale;
- 通过坐标绘制矩形 我们计算出矩形的位置和宽高就能准确画出改矩形。
矩形的位置用x,y 表示,x位置取两个两个位置最小的点,y位置同理。
矩形的宽高用两个坐标相减就能获得。整体代码如下
const areaAttr = {
x: Math.min(x1, x2),
y: Math.min(y1, y2),
width: Math.abs(x2 - x1),
height: Math.abs(y2 - y1),
}
3.修改矩形属性
selectionRectangle.setAttrs(areaAttr);
mouseup 事件
- 隐藏矩形
selectionRectangle.visible(false);
- 获取画布中所有节点
const shapes = stage.find('.node');
- 找出和矩形相交的节点
主要借助
haveIntersection这个方法。有兴趣可以自己实现改方法
const box = selectionRectangle.getClientRect();
const selected = shapes.filter((shape) =>
Konva.Util.haveIntersection(box, shape.getClientRect()),
);
- 添加到
Transformer中
canvas.tr.nodes([...selected]);
完整代码如下
- 框选
import canvas from '@/canvas-components/canvas';
import Konva from 'konva';
import Canvas from '../Canvas';
import { isBg } from './util';
let x1: number, y1: number, x2: number, y2: number;
let selectionRectangle: Konva.Rect;
export const rectangleVisible = () => {
return selectionRectangle && selectionRectangle.visible();
};
export const addRectangle = (layer: Konva.Layer) => {
selectionRectangle?.destroy();
selectionRectangle = new Konva.Rect({
fill: 'rgba(0,0,255,0.5)',
visible: false,
});
console.log('add', layer);
layer.add(selectionRectangle);
};
export const rectangleStart = (stage: Konva.Stage, canvas: Canvas) => {
// console.log('canvas', canvas);
console.log('rectangleStart=>');
addRectangle(canvas.layer);
const position = stage.getPointerPosition();
if (position) {
const scale = canvas.canvasAttr.scale;
let { x, y } = position;
console.log('position: ', x, y, canvas.canvasAttr.scale);
x = x / scale;
y = y / scale;
x1 = x;
y1 = y;
x2 = x;
y2 = y;
selectionRectangle.visible(true);
selectionRectangle.width(0);
selectionRectangle.height(0);
}
// layer.add(selectionRectangle);
};
export const rectangleMove = (stage: Konva.Stage, canvas: Canvas) => {
if (!selectionRectangle) {
return;
}
if (!selectionRectangle.visible()) {
return;
}
// console.log('move1312=>', selectionRectangle)
const position = stage.getPointerPosition();
if (position) {
const scale = canvas.canvasAttr.scale;
const { x, y } = position;
x2 = x / scale;
y2 = y / scale;
selectionRectangle.setAttrs({
x: Math.min(x1, x2),
y: Math.min(y1, y2),
width: Math.abs(x2 - x1),
height: Math.abs(y2 - y1),
});
}
};
export const rectangleEnd = (stage: Konva.Stage, canvas: Canvas) => {
console.log('rectangleEnd=>');
if (!selectionRectangle) {
return;
}
if (!selectionRectangle.visible()) {
return;
}
setTimeout(() => {
selectionRectangle.visible(false);
});
const shapes = stage.find('.node');
const box = selectionRectangle.getClientRect();
const selected = shapes.filter((shape) =>
Konva.Util.haveIntersection(box, shape.getClientRect()),
);
// console.log('selected shape', selected);
canvas.tr.nodes([...selected]);
canvas.layer.add(canvas.tr);
};
- 事件监听
stage.on('mousedown', (e) => {
// console.log('mousedownmousedown', e.evt.button)
// 判断鼠标左击
if (!isBg(e.target.attrs)) {
return;
}
console.log('e.evt.button', e.evt.button);
if (e.evt.button === 0) {
rectangleStart(stage, canvas);
}
});
stage.on('mousemove', () => {
rectangleMove(stage, canvas);
});
stage.on('mouseup', () => {
rectangleEnd(stage, canvas);
});
地址
交流沟通
建立了一个微信交流群,如需沟通讨论,请添加微信号q1454763497,备注image editor,我会拉你进群.
总结
以上我们实现了编辑器的框选功能。功能部分大致代码介绍上面已经描述出来,如需要查看更详细的内容,请移步fast-image-editor。 大家觉得有帮忙,请在github帮忙star一下。
历史文章
如果你觉得该文章不错,不妨
1、点赞,让更多的人也能看到这篇内容
2、关注我,让我们成为长期关系
3、关注公众号「前端有话说」,里面已有多篇原创文章,和开发工具,欢迎各位的关注,第一时间阅读我的文章