Fix manual resizing
This commit is contained in:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user