I have a need to adjust the KonvaJS Container/Stage in a similar way to a canvas element. I’ve provided an example below showcasing my exact need. In this code the source of the image to select is the canvas, and so I need the canvas and the Konva elements to dynamically update to the screen size and perfectly align while still maintaining dimensions of 1000×1000.
However, for some reason the Konva container’s (select-container) width is not adjusting the same way as the canvas does with the same css. In addition, it seems that the stage is not adjusting to the fit within the container. Is there any built in solution to fix my issue?
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/[email protected]/konva.min.js"></script>
<meta charset="utf-8" />
<title>Canvas to Konva Object</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #f0f0f0;
}
.over-container {
top: 100%;
display: flex;
flex-wrap: nowrap; /* Prevent wrapping */
flex-direction: column;
width: 100%;
height: 100vh;
align-items: center;
margin-top: 35px;
}
#canvas-container {
position: relative;
width: 100%;
overflow: hidden;
/* Aspect Ratio */
padding-top: 100%;
display: flex;
justify-content: center;
}
#select-container {
cursor: crosshair;
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%);
max-width: calc(100vw - 10px); /* Subtracting 5px margin on both sides */
/* Set explicitly or let JavaScript control */
border: 1px solid #571bfa;
flex: 1;
}
#canvas {
cursor: crosshair;
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%);
/* Adjust size based on viewport */
max-width: calc(100vw - 10px); /* Subtracting 5px margin on both sides */
touch-action: none;
image-rendering: pixelated;
flex: 1;
border: 1px solid #000;
}
</style>
</head>
<body>
<div class="over-container" id="Ocontainer">
<div id="canvas-container">
<canvas id="canvas"></canvas>
<div id="select-container"></div>
</div>
</div>
<script>
var width = 1000;
var height = 1000;
// Get the canvas and set dimensions
var canvas = document.getElementById('canvas');
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'blue';
ctx.fillRect(50, 50, 300, 200);
ctx.fillStyle = 'orange';
ctx.fillRect(400, 100, 200, 300);
// Create Konva stage and layer
var stage = new Konva.Stage({
container: 'select-container',
width: width,
height: height,
});
var layer = new Konva.Layer();
stage.add(layer);
var selectionRectangle = new Konva.Rect({
fill: 'rgba(0, 0, 255, 0.5)',
visible: false,
listening: false,
});
layer.add(selectionRectangle);
var x1, y1, x2, y2, selecting = false, activeObject = null;
var copiedObject = null;
var objects = [];
var transformers = []; // Keep track of transformers for cleanup
// Helper function to create a transformer for a given object
function createTransformer(object) {
const transformer = new Konva.Transformer({
nodes: [object],
});
layer.add(transformer);
transformer.update();
transformers.push(transformer); // Keep track of the transformer
return transformer;
}
stage.on('mousedown touchstart', (e) => {
if (e.target !== stage) return;
e.evt.preventDefault();
x1 = stage.getPointerPosition().x;
y1 = stage.getPointerPosition().y;
selecting = true;
selectionRectangle.setAttrs({
visible: true,
x: x1,
y: y1,
width: 0,
height: 0,
});
});
stage.on('mousemove touchmove', (e) => {
if (!selecting) return;
e.evt.preventDefault();
x2 = stage.getPointerPosition().x;
y2 = stage.getPointerPosition().y;
let width = Math.abs(x2 - x1);
let height = Math.abs(y2 - y1);
// Check if Shift key is held
if (e.evt.shiftKey) {
// Force square by setting width and height to the smaller of the two
const sideLength = Math.min(width, height);
width = sideLength;
height = sideLength;
// Adjust x2 and y2 to maintain the square shape
if (x2 < x1) x2 = x1 - width; // Adjust for leftward movement
else x2 = x1 + width; // Adjust for rightward movement
if (y2 < y1) y2 = y1 - height; // Adjust for upward movement
else y2 = y1 + height; // Adjust for downward movement
}
selectionRectangle.setAttrs({
x: Math.min(x1, x2),
y: Math.min(y1, y2),
width: width,
height: height,
});
layer.batchDraw();
});
stage.on('mouseup touchend', () => {
if (!selecting) return;
selecting = false;
selectionRectangle.visible(false);
const box = selectionRectangle.getClientRect();
const sx = box.x;
const sy = box.y;
const sw = box.width;
const sh = box.height;
if (sw === 0 || sh === 0) return;
// Get the selected area from the visible canvas
const imageData = ctx.getImageData(sx, sy, sw, sh);
// Remove the selected portion from the canvas
ctx.clearRect(sx, sy, sw, sh);
// Create an off-screen canvas to crop the image data
const tempCanvas = document.createElement('canvas');
tempCanvas.width = sw;
tempCanvas.height = sh;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.putImageData(imageData, 0, 0);
// Create a Konva.Image from the cropped area
const image = new Konva.Image({
x: sx,
y: sy,
image: tempCanvas,
draggable: true,
});
layer.add(image);
objects.push(image);
// Create a transformer for this object
createTransformer(image);
activeObject = image;
layer.batchDraw();
});
window.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'c' && activeObject) {
// Copy the active object
const clonedCanvas = document.createElement('canvas');
clonedCanvas.width = activeObject.image().width;
clonedCanvas.height = activeObject.image().height;
const clonedCtx = clonedCanvas.getContext('2d');
clonedCtx.drawImage(activeObject.image(), 0, 0);
copiedObject = {
x: activeObject.x() + 10,
y: activeObject.y() + 10,
image: clonedCanvas,
rotation: activeObject.rotation(),
scaleX: activeObject.scaleX(),
scaleY: activeObject.scaleY(),
};
}
if (e.ctrlKey && e.key === 'v' && copiedObject) {
// Paste the copied object
const newCopy = new Konva.Image({
x: copiedObject.x,
y: copiedObject.y,
image: copiedObject.image,
draggable: true,
rotation: copiedObject.rotation,
scaleX: copiedObject.scaleX,
scaleY: copiedObject.scaleY,
});
layer.add(newCopy);
objects.push(newCopy);
// Create a transformer for the new copy
createTransformer(newCopy);
activeObject = newCopy;
layer.batchDraw();
}
});
stage.on('click tap', (e) => {
if (activeObject && e.target === stage) {
// Apply all objects back to the canvas
objects.forEach((obj) => {
const image = obj.image(); // Get the underlying image
const rotation = obj.rotation(); // Rotation angle in degrees
// Use getClientRect to get transformed position
const clientRect = obj.getClientRect();
const boundingBoxCenterX = clientRect.x + clientRect.width / 2;
const boundingBoxCenterY = clientRect.y + clientRect.height / 2;
// Use the original image dimensions
const originalWidth = image.width;
const originalHeight = image.height;
// Use scaleX and scaleY for scaling
const scaledWidth = originalWidth * obj.scaleX();
const scaledHeight = originalHeight * obj.scaleY();
// Save the canvas context state
ctx.save();
// Translate to the bounding box center
ctx.translate(boundingBoxCenterX, boundingBoxCenterY);
// Rotate the canvas context around the center of the bounding box
ctx.rotate((rotation * Math.PI) / 180);
// Draw the object onto the canvas using the original dimensions and scaling
ctx.drawImage(
image,
-scaledWidth / 2, // Center the image horizontally
-scaledHeight / 2, // Center the image vertically
scaledWidth,
scaledHeight
);
// Restore the canvas context state
ctx.restore();
});
// Destroy all Konva objects and transformers
objects.forEach((obj) => obj.destroy());
transformers.forEach((transformer) => transformer.destroy());
transformers = [];
// Reset state variables
objects = [];
activeObject = null;
// Redraw the layer
layer.batchDraw();
}
});
const selectContainer = document.getElementById('select-container');
const canvasContainer = document.getElementById('canvas-container');
let imageSize = 1000;
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
window.addEventListener('load', resizeCanvas);
document.body.addEventListener('resize', function(event) {
document.body.style.zoom = 1;
});
function resizeCanvas() {
const screenWidth = window.innerWidth;
const screenHeight = window.innerHeight;
const spaceToBottom = window.innerHeight - (canvasContainer.offsetTop + canvasContainer.offsetHeight);
const topElementsHeight = screenHeight;
canvas.width = Math.round(imageSize);
canvas.height = Math.round(imageSize);
canvas.style.maxHeight = topElementsHeight + 'px';
canvas.style.height = topElementsHeight - spaceToBottom + 'px';
selectContainer.style.setProperty('width', Math.round(imageSize) + 'px', 'important');
selectContainer.style.setProperty('height', Math.round(imageSize) + 'px', 'important');
selectContainer.style.setProperty('max-height', topElementsHeight + 'px', 'important');
selectContainer.style.setProperty('height', topElementsHeight - spaceToBottom + 'px', 'important');
const newWidth = Math.round(imageSize);
const newHeight = Math.round(imageSize);
stage.width(newWidth);
stage.height(newHeight);
stage.batchDraw();
redrawCanvas();
}
function redrawCanvas() {
// Clear the main canvas and set the background to white
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'blue';
ctx.fillRect(50, 50, 300, 200);
ctx.fillStyle = 'orange';
ctx.fillRect(400, 100, 200, 300);
}
</script>
</body>
</html>