preface

We haven’t updated our image editor for a while. Recently, we did a refactoring and improved the box selection function of the nodes. The refactoring mainly implemented the Core SDK, so we can implement the editor with multiple frameworks like React and Vue. Today we mainly look at the node box selection function, node box selection support shift+ left mouse button and rectangular box selection implement multiple node box selection. We’ll talk about the core SDK features later.

demo

Demo address

Code implementation

The movement of multiple nodes is achieved mainly through the Nodes attribute in Knova’s Transformer.

Shift + left mouse button

First we listen for click events on the canvas, listening for multiple nodes to be selected when the mouse clicks and the Shift key is pressed.

stage.on('click', (event) => { if (rectangleVisible()) { return; } // Check if it is not the background canvas of the point that triggers the node. IsBg (event. Target. Attrs)) {/ / have to press the shift key const metaPressed = event. The evt. ShiftKey | | event. The evt. CtrlKey | | event.evt.metaKey; // Whether const isSelected = canvas.tr.nodes().indexof (event.target) >= 0; // The node is not selected and the Shift key is not pressed. Add the first memory if (! metaPressed && ! isSelected) { canvas.tr.nodes([event.target]); } else if (metaPressed && isSelected) {// Shifit is pressed and const Nodes = canvas.tr.nodes().slice(); nodes.splice(nodes.indexOf(event.target), 1); canvas.tr.nodes(nodes); } else if (metaPressed && ! Const Nodes = canvas.tr.nodes().concat([event.target]); canvas.tr.nodes(nodes); } canvas.layer.add(canvas.tr); } else {// Click on other fields to clear canvas.tr.Nodes ([]); }});Copy the code

Shift + left mouse button to here, relatively easy. Let’s talk about box selection to realize multiple selection of nodes

Rectangular box to choose

First of all, we will implement our rectangle box. The rectangle box is mainly realized through three events, mousedown, Mousemove and Mouseup. The main principle is that Mousedown records the position of the mouse,mousemove draws the rectangle through the change of coordinates. Mouseup calculates which nodes are in our rectangle and selects them. So let’s start implementing it

Create a shadow rectangle and add it to the canvas

SelectionRectangle = new konva. Rect({fill: 'rgba(0,0,255,0.5)', visible: false,}); layer.add(selectionRectangle);Copy the code

mousedownThe event

  1. Gets the position of the mouse on the canvas
const position = stage.getPointerPosition();
Copy the code
  1. Record the location

Note: pay attention to the scaling of the canvas, otherwise the position will be inaccurate

Gets the scaled coordinates

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

Copy the code

Record the start position and drag position

// 起始位置
x1 = x;
y1 = y;
// 移动后的位置
x2 = x;
y2 = y;
Copy the code
  1. Make rectangle visible
selectionRectangle.visible(true);
selectionRectangle.width(0);
selectionRectangle.height(0);
Copy the code

mousemoveThe event

  1. Gets the position of the mouse on the canvas and calculates the scaled position
const position = stage.getPointerPosition();
x2 = x / scale;
y2 = y / scale;
Copy the code
  1. Draws a rectangle by coordinates

We can calculate the position and width of the rectangle and draw it exactly.

The position of the rectangle is denoted by x and y, so let’s take the two smallest points at x and y, and do the same thing at y.

The width and height of a rectangle can be obtained by subtracting two coordinates. The overall code is as follows

const areaAttr = {
      x: Math.min(x1, x2),
      y: Math.min(y1, y2),
      width: Math.abs(x2 - x1),
      height: Math.abs(y2 - y1),
}
Copy the code

3. Modify the rectangle properties

 selectionRectangle.setAttrs(areaAttr);
Copy the code

Mouseup event

  1. Hidden rectangles
selectionRectangle.visible(false);
Copy the code
  1. Gets all the nodes in the canvas
  const shapes = stage.find('.node');
Copy the code
  1. Find the nodes that intersect the rectangle

Use the haveIntersection method. If you are interested, you can implement the method yourself

  const box = selectionRectangle.getClientRect();
  const selected = shapes.filter((shape) =>
    Konva.Util.haveIntersection(box, shape.getClientRect()),
  );
Copy the code
  1. Added to theTransformerIn the
 canvas.tr.nodes([...selected]);
Copy the code

The complete code is as follows

  • Box to choose
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); };Copy the code
  • Event listeners
Stage. on('mousedown', (e) => {// console.log('mousedownmousedown', e.e.button) 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); });Copy the code

address

  • Demo address
  • The code address

communication

A wechat communication group has been established. If you need communication and discussion, please add wechat id Q1454763497 and note image Editor, I will include you in the group.

conclusion

Above we have realized the box selection function of the editor. The general code introduction of the functional part has been described above. For more details, please go to the fast-image-editor. If you think it is helpful, please help star on Github.

Article history

  • (Open source) Two weekends to write a picture editor
  • (open source) added helper lines to the image editor
  • (open source) Added an undo redo feature to the image editor