<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas Select and Modify Tool</title>
<style>
/* Body styling */
body {
margin: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background: #FFF;
font-family: Arial, sans-serif;
overflow: hidden;
}
/* Canvas container */
#canvas, #overlayCanvas {
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2), 0 6px 20px rgba(0, 0, 0, 0.19);
border: 2px solid #ffffff;
border-radius: 8px;
}
/* Set a fixed size for the canvases */
canvas {
width: 800px;
height: 600px;
}
/* Overlay canvas styling */
#overlayCanvas {
z-index: 10;
pointer-events: none; /* Ensures overlay canvas does not block interactions */
}
</style>
</head>
<body>
<canvas id="canvas" width="800" height="600"></canvas>
<canvas id="overlayCanvas" width="800" height="600"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const overlayCanvas = document.getElementById('overlayCanvas');
const overlayCtx = overlayCanvas.getContext('2d');
let isSelecting = false;
let isMoving = false;
let isResizing = false;
let isRotating = false;
let resizeHandle = null;
let rotationAngle = 0;
let selection = null;
let mouseDownCoords = null;
let offset = { x: 0, y: 0 };
let selectionImage = null; // Image of the selected content
const resizeHandleSize = 10;
const rotationHandleOffset = 30;
// Initial canvas drawing
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.arc(400, 300, 100, 0, Math.PI * 2);
ctx.fill();
canvas.addEventListener('mousedown', (e) => {
const x = e.offsetX;
const y = e.offsetY;
if (!selection) {
isSelecting = true;
mouseDownCoords = { x, y };
selection = {
x: mouseDownCoords.x,
y: mouseDownCoords.y,
width: 0,
height: 0,
originalWidth: 0,
originalHeight: 0
};
} else {
const handle = getHandleUnderCursor(x, y);
if (handle) {
isResizing = true;
resizeHandle = handle;
selection.originalWidth = selection.width;
selection.originalHeight = selection.height;
} else if (isInsideSelection(x, y)) {
isMoving = true;
offset = {
x: x - selection.x,
y: y - selection.y,
};
} else if (isRotationHandle(x, y)) {
isRotating = true;
const centerX = selection.x + selection.width / 2;
const centerY = selection.y + selection.height / 2;
rotationAngle = Math.atan2(y - centerY, x - centerX);
} else {
applySelection();
}
}
});
canvas.addEventListener('mousemove', (e) => {
if (isSelecting) {
updateSelection(mouseDownCoords.x, mouseDownCoords.y, e.offsetX, e.offsetY);
} else if (isMoving) {
moveSelection(e.offsetX, e.offsetY);
} else if (isResizing) {
resizeSelection(e.offsetX, e.offsetY);
} else if (isRotating) {
rotateSelection(e.offsetX, e.offsetY);
}
});
canvas.addEventListener('mouseup', () => {
if (isSelecting) {
finalizeSelection();
}
isSelecting = isMoving = isResizing = isRotating = false;
resizeHandle = null;
});
function updateSelection(startX, startY, currentX, currentY) {
selection.x = Math.min(startX, currentX);
selection.y = Math.min(startY, currentY);
selection.width = Math.abs(startX - currentX);
selection.height = Math.abs(startY - currentY);
drawOverlay();
}
function moveSelection(x, y) {
selection.x = x - offset.x;
selection.y = y - offset.y;
drawOverlay();
}
function resizeSelection(x, y) {
const centerX = selection.x + selection.width / 2;
const centerY = selection.y + selection.height / 2;
// Transform the mouse position into the selection's local space
const { x: localX, y: localY } = getRotatedPoint(x, y, -rotationAngle, centerX, centerY);
// Adjust selection bounds based on the handle being used
if (resizeHandle === 'top') {
const bottomY = selection.y + selection.height; // Keep the bottom stationary
selection.y = Math.min(localY, bottomY);
selection.height = Math.abs(bottomY - selection.y);
} else if (resizeHandle === 'bottom') {
const topY = selection.y; // Keep the top stationary
selection.height = Math.abs(localY - topY);
} else if (resizeHandle === 'left') {
const rightX = selection.x + selection.width; // Keep the right stationary
selection.x = Math.min(localX, rightX);
selection.width = Math.abs(rightX - selection.x);
} else if (resizeHandle === 'right') {
const leftX = selection.x; // Keep the left stationary
selection.width = Math.abs(localX - leftX);
} else if (resizeHandle === 'top-left') {
const bottomRight = { x: selection.x + selection.width, y: selection.y + selection.height }; // Keep bottom-right stationary
selection.x = Math.min(localX, bottomRight.x);
selection.y = Math.min(localY, bottomRight.y);
selection.width = Math.abs(bottomRight.x - selection.x);
selection.height = Math.abs(bottomRight.y - selection.y);
} else if (resizeHandle === 'top-right') {
const bottomLeft = { x: selection.x, y: selection.y + selection.height }; // Keep bottom-left stationary
selection.y = Math.min(localY, bottomLeft.y);
selection.width = Math.abs(localX - bottomLeft.x);
selection.height = Math.abs(bottomLeft.y - selection.y);
} else if (resizeHandle === 'bottom-left') {
const topRight = { x: selection.x + selection.width, y: selection.y }; // Keep top-right stationary
selection.x = Math.min(localX, topRight.x);
selection.height = Math.abs(localY - topRight.y);
selection.width = Math.abs(topRight.x - selection.x);
} else if (resizeHandle === 'bottom-right') {
const topLeft = { x: selection.x, y: selection.y }; // Keep top-left stationary
selection.width = Math.abs(localX - topLeft.x);
selection.height = Math.abs(localY - topLeft.y);
}
// No need to adjust the center of rotation because the opposite side is stationary
drawOverlay();
}
function rotateSelection(x, y) {
const centerX = selection.x + selection.width / 2;
const centerY = selection.y + selection.height / 2;
const angle = Math.atan2(y - centerY, x - centerX);
rotationAngle = angle + Math.PI / 2;
drawOverlay();
}
function finalizeSelection() {
const tempCanvas = document.createElement('canvas');
tempCanvas.width = selection.width;
tempCanvas.height = selection.height;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.drawImage(
canvas,
selection.x,
selection.y,
selection.width,
selection.height,
0,
0,
selection.width,
selection.height
);
selectionImage = new Image();
selectionImage.src = tempCanvas.toDataURL();
drawOverlay();
}
function applySelection() {
if (selection) {
ctx.save();
ctx.translate(selection.x + selection.width / 2, selection.y + selection.height / 2);
ctx.rotate(rotationAngle);
if (selectionImage) {
ctx.drawImage(
selectionImage,
0,
0,
selectionImage.width,
selectionImage.height,
-selection.width / 2,
-selection.height / 2,
selection.width,
selection.height
);
}
ctx.restore();
selectionImage = null;
selection = null;
rotationAngle = 0;
overlayCtx.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
}
}
function drawOverlay() {
overlayCtx.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
if (selection) {
overlayCtx.save();
overlayCtx.translate(selection.x + selection.width / 2, selection.y + selection.height / 2);
overlayCtx.rotate(rotationAngle);
if (selectionImage) {
overlayCtx.drawImage(
selectionImage,
0,
0,
selectionImage.width,
selectionImage.height,
-selection.width / 2,
-selection.height / 2,
selection.width,
selection.height
);
}
overlayCtx.restore();
drawHandles();
}
}
function drawHandles() {
if (!selection) return;
const corners = getRotatedCorners();
const edges = getRotatedEdges(corners);
const centerX = selection.x + selection.width / 2;
const centerY = selection.y + selection.height / 2;
const rotation = getRotatedPoint(
selection.x + selection.width / 2,
selection.y - rotationHandleOffset,
rotationAngle,
centerX,
centerY
);
overlayCtx.fillStyle = 'red';
corners.forEach(point => {
overlayCtx.fillRect(point.x - resizeHandleSize / 2, point.y - resizeHandleSize / 2, resizeHandleSize, resizeHandleSize);
});
edges.forEach(point => {
overlayCtx.fillRect(point.x - resizeHandleSize / 2, point.y - resizeHandleSize / 2, resizeHandleSize, resizeHandleSize);
});
overlayCtx.fillStyle = 'green';
overlayCtx.beginPath();
overlayCtx.arc(rotation.x, rotation.y, resizeHandleSize / 2, 0, Math.PI * 2);
overlayCtx.fill();
}
function getHandleUnderCursor(x, y) {
const corners = getRotatedCorners();
const edges = getRotatedEdges(corners);
for (let i = 0; i < corners.length; i++) {
const corner = corners[i];
if (
x >= corner.x - resizeHandleSize / 2 &&
x <= corner.x + resizeHandleSize / 2 &&
y >= corner.y - resizeHandleSize / 2 &&
y <= corner.y + resizeHandleSize / 2
) {
return ['top-left', 'top-right', 'bottom-left', 'bottom-right'][i];
}
}
for (let i = 0; i < edges.length; i++) {
const edge = edges[i];
if (
x >= edge.x - resizeHandleSize / 2 &&
x <= edge.x + resizeHandleSize / 2 &&
y >= edge.y - resizeHandleSize / 2 &&
y <= edge.y + resizeHandleSize / 2
) {
return ['top', 'right', 'bottom', 'left'][i];
}
}
return null;
}
function getRotatedCorners() {
const centerX = selection.x + selection.width / 2;
const centerY = selection.y + selection.height / 2;
return [
getRotatedPoint(selection.x, selection.y, rotationAngle, centerX, centerY),
getRotatedPoint(selection.x + selection.width, selection.y, rotationAngle, centerX, centerY),
getRotatedPoint(selection.x, selection.y + selection.height, rotationAngle, centerX, centerY),
getRotatedPoint(selection.x + selection.width, selection.y + selection.height, rotationAngle, centerX, centerY),
];
}
function getRotatedEdges(corners) {
return [
midpoint(corners[0], corners[1]),
midpoint(corners[1], corners[3]),
midpoint(corners[2], corners[3]),
midpoint(corners[0], corners[2]),
];
}
function getRotatedPoint(x, y, angle, centerX, centerY) {
const dx = x - centerX;
const dy = y - centerY;
const rotatedX = dx * Math.cos(angle) - dy * Math.sin(angle) + centerX;
const rotatedY = dx * Math.sin(angle) + dy * Math.cos(angle) + centerY;
return { x: rotatedX, y: rotatedY };
}
function midpoint(p1, p2) {
return { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 };
}
function isInsideSelection(x, y) {
const centerX = selection.x + selection.width / 2;
const centerY = selection.y + selection.height / 2;
const { x: localX, y: localY } = getRotatedPoint(x, y, -rotationAngle, centerX, centerY);
return (
localX >= selection.x &&
localX <= selection.x + selection.width &&
localY >= selection.y &&
localY <= selection.y + selection.height
);
}
function isRotationHandle(x, y) {
const centerX = selection.x + selection.width / 2;
const centerY = selection.y + selection.height / 2;
const rotation = getRotatedPoint(
selection.x + selection.width / 2,
selection.y - rotationHandleOffset,
rotationAngle,
centerX,
centerY
);
return (
x >= rotation.x - resizeHandleSize / 2 &&
x <= rotation.x + resizeHandleSize / 2 &&
y >= rotation.y - resizeHandleSize / 2 &&
y <= rotation.y + resizeHandleSize / 2
);
}
</script>
</body>
</html>