Fix manual resizing

This commit is contained in:
Roman Krček
2025-09-08 15:19:05 +02:00
parent d845021f7e
commit c6c3bbc024

View File

@@ -35,9 +35,7 @@
// Interaction state
let isDragging = false;
let isResizing = false;
let dragStart = { x: 0, y: 0 };
let resizeHandle = '';
// Canvas dimensions
let canvasWidth = 600;
@@ -135,25 +133,6 @@
ctx.strokeStyle = '#3b82f6';
ctx.lineWidth = 2;
ctx.strokeRect(crop.x, crop.y, crop.width, crop.height);
// Draw resize handles
const handleSize = 12; // Increased from 8 for easier grabbing
ctx.fillStyle = '#3b82f6';
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 1;
// Corner handles with white borders for better visibility
const handles = [
{ x: crop.x - handleSize/2, y: crop.y - handleSize/2, cursor: 'nw-resize' },
{ x: crop.x + crop.width - handleSize/2, y: crop.y - handleSize/2, cursor: 'ne-resize' },
{ x: crop.x - handleSize/2, y: crop.y + crop.height - handleSize/2, cursor: 'sw-resize' },
{ x: crop.x + crop.width - handleSize/2, y: crop.y + crop.height - handleSize/2, cursor: 'se-resize' },
];
handles.forEach(handle => {
ctx.fillRect(handle.x, handle.y, handleSize, handleSize);
ctx.strokeRect(handle.x, handle.y, handleSize, handleSize);
});
}
function getMousePos(e: MouseEvent) {
@@ -165,31 +144,12 @@
}
function isInCropArea(x: number, y: number) {
return x >= crop.x && x <= crop.x + crop.width &&
y >= crop.y && y <= crop.y + crop.height;
}
function getResizeHandle(x: number, y: number) {
const handleSize = 12; // Match the drawing size
const tolerance = handleSize;
if (Math.abs(x - crop.x) <= tolerance && Math.abs(y - crop.y) <= tolerance) return 'nw';
if (Math.abs(x - (crop.x + crop.width)) <= tolerance && Math.abs(y - crop.y) <= tolerance) return 'ne';
if (Math.abs(x - crop.x) <= tolerance && Math.abs(y - (crop.y + crop.height)) <= tolerance) return 'sw';
if (Math.abs(x - (crop.x + crop.width)) <= tolerance && Math.abs(y - (crop.y + crop.height)) <= tolerance) return 'se';
return '';
return x >= crop.x && x <= crop.x + crop.width && y >= crop.y && y <= crop.y + crop.height;
}
function handleMouseDown(e: MouseEvent) {
const pos = getMousePos(e);
const handle = getResizeHandle(pos.x, pos.y);
if (handle) {
isResizing = true;
resizeHandle = handle;
dragStart = pos;
} else if (isInCropArea(pos.x, pos.y)) {
if (isInCropArea(pos.x, pos.y)) {
isDragging = true;
dragStart = { x: pos.x - crop.x, y: pos.y - crop.y };
}
@@ -198,86 +158,13 @@
function handleMouseMove(e: MouseEvent) {
const pos = getMousePos(e);
if (isResizing) {
const dx = pos.x - dragStart.x;
const dy = pos.y - dragStart.y;
const newCrop = { ...crop };
// Use primary axis movement for more predictable resizing
switch (resizeHandle) {
case 'nw':
// Use the dominant movement direction
const primaryDelta = Math.abs(dx) > Math.abs(dy) ? dx : dy * cropRatio;
const newWidth = Math.max(20, crop.width - primaryDelta);
const newHeight = newWidth / cropRatio;
newCrop.x = Math.max(0, crop.x + crop.width - newWidth);
newCrop.y = Math.max(0, crop.y + crop.height - newHeight);
newCrop.width = newWidth;
newCrop.height = newHeight;
break;
case 'ne':
// For NE, primarily follow horizontal movement
const newWidthNE = Math.max(20, crop.width + dx);
const newHeightNE = newWidthNE / cropRatio;
newCrop.width = newWidthNE;
newCrop.height = newHeightNE;
newCrop.y = Math.max(0, crop.y + crop.height - newHeightNE);
break;
case 'sw':
// For SW, primarily follow horizontal movement
const newWidthSW = Math.max(20, crop.width - dx);
const newHeightSW = newWidthSW / cropRatio;
newCrop.x = Math.max(0, crop.x + crop.width - newWidthSW);
newCrop.width = newWidthSW;
newCrop.height = newHeightSW;
break;
case 'se':
// For SE, primarily follow horizontal movement
const newWidthSE = Math.max(20, crop.width + dx);
const newHeightSE = newWidthSE / cropRatio;
newCrop.width = newWidthSE;
newCrop.height = newHeightSE;
break;
}
// Ensure crop stays within canvas bounds
if (newCrop.x + newCrop.width > canvasWidth) {
newCrop.width = canvasWidth - newCrop.x;
newCrop.height = newCrop.width / cropRatio;
}
if (newCrop.y + newCrop.height > canvasHeight) {
newCrop.height = canvasHeight - newCrop.y;
newCrop.width = newCrop.height * cropRatio;
}
// Adjust position if crop extends beyond bounds after resizing
if (newCrop.x + newCrop.width > canvasWidth) {
newCrop.x = canvasWidth - newCrop.width;
}
if (newCrop.y + newCrop.height > canvasHeight) {
newCrop.y = canvasHeight - newCrop.height;
}
crop = newCrop;
drawCanvas();
} else if (isDragging) {
if (isDragging) {
crop.x = Math.max(0, Math.min(canvasWidth - crop.width, pos.x - dragStart.x));
crop.y = Math.max(0, Math.min(canvasHeight - crop.height, pos.y - dragStart.y));
drawCanvas();
} else {
// Update cursor based on hover state
const handle = getResizeHandle(pos.x, pos.y);
if (handle) {
canvas.style.cursor = handle + '-resize';
} else if (isInCropArea(pos.x, pos.y)) {
if (isInCropArea(pos.x, pos.y)) {
canvas.style.cursor = 'move';
} else {
canvas.style.cursor = 'default';
@@ -287,11 +174,39 @@
function handleMouseUp() {
isDragging = false;
isResizing = false;
resizeHandle = '';
canvas.style.cursor = 'default';
}
function zoom(factor: number) {
const center = {
x: crop.x + crop.width / 2,
y: crop.y + crop.height / 2
};
let newWidth = crop.width * factor;
let newHeight = newWidth / cropRatio;
// Clamp to min/max size
newWidth = Math.max(20, Math.min(canvasWidth, newWidth));
newHeight = newWidth / cropRatio;
if (newHeight > canvasHeight) {
newHeight = canvasHeight;
newWidth = newHeight * cropRatio;
}
crop.width = newWidth;
crop.height = newHeight;
crop.x = center.x - newWidth / 2;
crop.y = center.y - newHeight / 2;
// Ensure it stays within bounds after zooming
crop.x = Math.max(0, Math.min(canvasWidth - crop.width, crop.x));
crop.y = Math.max(0, Math.min(canvasHeight - crop.height, crop.y));
drawCanvas();
}
function handleSave() {
// Scale crop rectangle back to original image dimensions
const scaleX = image.width / canvasWidth;
@@ -352,16 +267,52 @@
</button>
</div>
<div class="mb-4 p-2 rounded-md text-center">
<div class="relative mb-4 p-2 rounded-md text-center">
<canvas
bind:this={canvas}
onmousedown={handleMouseDown}
onmousemove={handleMouseMove}
onmouseup={handleMouseUp}
onmouseleave={handleMouseUp}
class="mx-auto cursor-move"
class="mx-auto"
style="max-width: 100%; height: auto;"
></canvas>
<div class="absolute bottom-4 right-4 flex space-x-2">
<button
onclick={() => zoom(1 / 1.1)}
class="flex h-10 w-10 items-center justify-center rounded-full bg-gray-700 bg-opacity-50 text-white hover:bg-opacity-75"
aria-label="Zoom out"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="h-6 w-6"
>
<path
fill-rule="evenodd"
d="M4 10a.75.75 0 01.75-.75h10.5a.75.75 0 010 1.5H4.75A.75.75 0 014 10z"
clip-rule="evenodd"
/>
</svg>
</button>
<button
onclick={() => zoom(1.1)}
class="flex h-10 w-10 items-center justify-center rounded-full bg-gray-700 bg-opacity-50 text-white hover:bg-opacity-75"
aria-label="Zoom in"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="h-6 w-6"
>
<path
d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
/>
</svg>
</button>
</div>
</div>
<div class="flex justify-end space-x-3">