HTML5 Canvas Shape Resize and Transform Limits
To limit or change resize and transform behavior you can use boundBoxFunc property.
It works a bit similar to dragBoundFunc.
Instructions: Try to resize a shape. You will see that its width is limited to 200.
The demo clamps the new bounding box to the limit instead of rejecting it. If you simply return oldBox when the size is over the limit, a fast mouse move overshoots the boundary in one event, and the shape gets stuck below the limit. Interpolating between oldBox and newBox lands exactly on the limit and keeps the opposite side of the shape pinned for every anchor.
Also you can control movement of every anchors individually. See Resize Snap Demo.
- Vanilla
- React
- Vue
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);
const rect = new Konva.Rect({
x: 50,
y: 50,
width: 100,
height: 100,
fill: 'yellow',
stroke: 'black',
draggable: true,
});
layer.add(rect);
const tr = new Konva.Transformer({
nodes: [rect],
boundBoxFunc: (oldBox, newBox) => {
// limit resize to a maximum width of 200.
// clamp the box between oldBox and newBox instead of
// rejecting it, so a fast drag still lands exactly on the limit
if (newBox.width > 200) {
const t = (200 - oldBox.width) / (newBox.width - oldBox.width);
return {
x: oldBox.x + t * (newBox.x - oldBox.x),
y: oldBox.y + t * (newBox.y - oldBox.y),
width: 200,
height: oldBox.height + t * (newBox.height - oldBox.height),
rotation: newBox.rotation,
};
}
return newBox;
},
});
layer.add(tr);
import { Stage, Layer, Rect, Transformer } from 'react-konva';
import { useRef, useEffect, useState } from 'react';
const App = () => {
const [rectPosition, setRectPosition] = useState({ x: 50, y: 50 });
const rectRef = useRef();
const trRef = useRef();
useEffect(() => {
trRef.current.nodes([rectRef.current]);
}, []);
return (
<Stage width={window.innerWidth} height={window.innerHeight}>
<Layer>
<Rect
x={rectPosition.x}
y={rectPosition.y}
width={100}
height={100}
fill="yellow"
stroke="black"
draggable
ref={rectRef}
onDragEnd={(e) => setRectPosition(e.target.position())}
onTransformEnd={(e) => setRectPosition(e.target.position())}
/>
<Transformer
ref={trRef}
boundBoxFunc={(oldBox, newBox) => {
// limit resize to a maximum width of 200.
// clamp the box between oldBox and newBox instead of
// rejecting it, so a fast drag still lands exactly on the limit
if (newBox.width > 200) {
const t = (200 - oldBox.width) / (newBox.width - oldBox.width);
return {
x: oldBox.x + t * (newBox.x - oldBox.x),
y: oldBox.y + t * (newBox.y - oldBox.y),
width: 200,
height: oldBox.height + t * (newBox.height - oldBox.height),
rotation: newBox.rotation,
};
}
return newBox;
}}
/>
</Layer>
</Stage>
);
};
export default App;
<template>
<v-stage :config="stageSize">
<v-layer>
<v-rect
:config="rectConfig"
ref="rectRef"
/>
<v-transformer
:config="transformerConfig"
ref="transformerRef"
/>
</v-layer>
</v-stage>
</template>
<script setup>
import { ref, onMounted } from 'vue';
const stageSize = {
width: window.innerWidth,
height: window.innerHeight
};
const rectConfig = {
x: 50,
y: 50,
width: 100,
height: 100,
fill: 'yellow',
stroke: 'black',
draggable: true
};
const transformerConfig = {
boundBoxFunc: (oldBox, newBox) => {
// limit resize to a maximum width of 200.
// clamp the box between oldBox and newBox instead of
// rejecting it, so a fast drag still lands exactly on the limit
if (newBox.width > 200) {
const t = (200 - oldBox.width) / (newBox.width - oldBox.width);
return {
x: oldBox.x + t * (newBox.x - oldBox.x),
y: oldBox.y + t * (newBox.y - oldBox.y),
width: 200,
height: oldBox.height + t * (newBox.height - oldBox.height),
rotation: newBox.rotation,
};
}
return newBox;
}
};
const rectRef = ref(null);
const transformerRef = ref(null);
onMounted(() => {
transformerRef.value.getNode().nodes([rectRef.value.getNode()]);
});
</script>