Skip to main content

HTML5 Canvas Shape select, resize and rotate

Transformer is a special kind of Konva.Group. It allows you easily resize and rotate any node or set of nodes.

To enable it you need to:

  1. Create new instance with new Konva.Transformer()
  2. Add it to layer
  3. attach to node with transformer.nodes([shape]);
  4. Update the layer with layer.batchDraw()

Note: Transforming tool is not changing width and height properties of nodes when you resize them. Instead it changes scaleX and scaleY properties.

Instructions: Try to resize and rotate shapes. Click on empty area to remove selection. Use SHIFT or CTRL to add/remove shapes into/from selection. Try to select area on a canvas.

import Konva from 'konva';

const width = window.innerWidth;
const height = window.innerHeight;

const stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});

const layer = new Konva.Layer();
stage.add(layer);

// create rectangle

const rect1 = new Konva.Rect({
x: 60,
y: 60,
width: 100,
height: 90,
fill: 'red',
name: 'rect',
draggable: true,
});
layer.add(rect1);

const rect2 = new Konva.Rect({
x: 250,
y: 100,
width: 150,
height: 90,
fill: 'green',
name: 'rect',
draggable: true,
});
layer.add(rect2);

// create transformer

const tr = new Konva.Transformer();
layer.add(tr);

// add a new feature, lets add ability to draw selection rectangle

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

let x1, y1, x2, y2;
stage.on('mousedown touchstart', (e) => {
// do nothing if we mousedown on any shape

if (e.target !== stage) {
return;
}
x1 = stage.getPointerPosition().x;
y1 = stage.getPointerPosition().y;
x2 = stage.getPointerPosition().x;
y2 = stage.getPointerPosition().y;

selectionRectangle.visible(true);
selectionRectangle.width(0);
selectionRectangle.height(0);
});

stage.on('mousemove touchmove', () => {
// do nothing if we didn't start selection

if (!selectionRectangle.visible()) {
return;
}
x2 = stage.getPointerPosition().x;
y2 = stage.getPointerPosition().y;

selectionRectangle.setAttrs({
x: Math.min(x1, x2),
y: Math.min(y1, y2),
width: Math.abs(x2 - x1),
height: Math.abs(y2 - y1),
});
});

stage.on('mouseup touchend', () => {
// do nothing if we didn't start selection

if (!selectionRectangle.visible()) {
return;
}
// update visibility in timeout, so we can check it in click event

setTimeout(() => {
selectionRectangle.visible(false);
});

var shapes = stage.find('.rect');
var box = selectionRectangle.getClientRect();
var selected = shapes.filter((shape) =>
Konva.Util.haveIntersection(box, shape.getClientRect())
);
tr.nodes(selected);
});

// clicks should select/deselect shapes

stage.on('click tap', function (e) {
// if we are selecting with rect, do nothing

if (selectionRectangle.visible()) {
return;
}

// if click on empty area - remove all selections

if (e.target === stage) {
tr.nodes([]);
return;
}

// do nothing if clicked NOT on our rectangles

if (!e.target.hasName('rect')) {
return;
}

// do we pressed shift or ctrl?

const metaPressed = e.evt.shiftKey || e.evt.ctrlKey || e.evt.metaKey;
const isSelected = tr.nodes().indexOf(e.target) >= 0;

if (!metaPressed && !isSelected) {
// if no key pressed and the node is not selected

// select just one

tr.nodes([e.target]);
} else if (metaPressed && isSelected) {
// if we pressed keys and node was selected

// we need to remove it from selection:

const nodes = tr.nodes().slice(); // use slice to have new copy of array

// remove node from array

nodes.splice(nodes.indexOf(e.target), 1);
tr.nodes(nodes);
} else if (metaPressed && !isSelected) {
// add the node into selection

const nodes = tr.nodes().concat([e.target]);
tr.nodes(nodes);
}
});