阅读 754

(开源)给图片编辑器添加了【框选节点】功能

前言

有段时间没更新我们的图片编辑器了,最近做了重构和完善了节点的框选功能,重构主要实现了core sdk,方便之后可以React和Vue多个框架实现和兴编辑器。今天我们主要看下节点框选功能,节点框选支持shift+鼠标左键矩形框选实行多个节点框选。后续会介绍core sdk功能。

演示

演示地址

select.gif

代码实现

主要是通过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,mousemovemouseup事件,主要原理为mousedown记录鼠标的位置,mousemove通过坐标的变动绘制矩形,mouseup 计算哪些节点在我们的矩形范围内,然后实现选中。下面我们开始来实现

创建一个影藏的矩形,添加到画布当中

selectionRectangle = new Konva.Rect({
    fill: 'rgba(0,0,255,0.5)',
    visible: false,
 });
 layer.add(selectionRectangle);
复制代码

mousedown事件

  1. 获取鼠标在画布中的位置
const position = stage.getPointerPosition();
复制代码
  1. 记录位置

注意: 这里要关注下画布的缩放,否则位置会算的不准确

获取到缩放后的坐标

 let { x, y } = position;
 x = x / scale;
 y = y / scale;

复制代码

记录起始位置和拖动后的位置

// 起始位置
x1 = x;
y1 = y;
// 移动后的位置
x2 = x;
y2 = y;
复制代码
  1. 设置矩形可见
selectionRectangle.visible(true);
selectionRectangle.width(0);
selectionRectangle.height(0);
复制代码

mousemove 事件

  1. 获取鼠标在画布中的位置并计算出缩放后的位置
const position = stage.getPointerPosition();
x2 = x / scale;
y2 = y / scale;
复制代码
  1. 通过坐标绘制矩形

我们计算出矩形的位置和宽高就能准确画出改矩形。

矩形的位置用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 事件

  1. 隐藏矩形
selectionRectangle.visible(false);
复制代码
  1. 获取画布中所有节点
  const shapes = stage.find('.node');
复制代码
  1. 找出和矩形相交的节点

主要借助 haveIntersection 这个方法。有兴趣可以自己实现改方法

  const box = selectionRectangle.getClientRect();
  const selected = shapes.filter((shape) =>
    Konva.Util.haveIntersection(box, shape.getClientRect()),
  );
复制代码
  1. 添加到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、关注公众号「前端有话说」,里面已有多篇原创文章,和开发工具,欢迎各位的关注,第一时间阅读我的文章

文章分类
前端
文章标签