Как преобразовать объекты холста, сохраняя при этом напротив стороны фиксированной?Javascript

Форум по Javascript
Ответить Пред. темаСлед. тема
Anonymous
 Как преобразовать объекты холста, сохраняя при этом напротив стороны фиксированной?

Сообщение Anonymous »

Итак, моя идея состоит в том, чтобы сделать несколько простого редактора Canvas.

Моя текущая проблема заключается в том, что я не могу правильно изменить размер своего объекта. и не с края, я перетаскиваю.

Вот что у меня есть:



и вот что я ожидаю:


my code:
< /p>





Canvas Object Editor

body { display: flex; font-family: Arial, sans-serif; }
#canvasContainer {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
canvas { border: 1px solid black; }

.transform-handle{
width: 8px;
height: 8px;
background-color: lightblue;
position: absolute;
cursor: grab;
z-index: 1000;
border: 1px solid #007BFF;
border-radius: 2px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transform: translate(-50%, -50%);
}
.rotate-handle {
width: 8px;
height: 8px;
background-color: rgb(225, 173, 230);
position: absolute;
cursor: grab;
z-index: 1000;
border: 1px solid #ff00aa;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transform: translate(-50%, -50%);
}







const canvas = document.getElementById("editorCanvas");
const ctx = canvas.getContext("2d");
const canvasContainer = document.getElementById("canvasContainer");

let isDragging = false;
let offsetX, offsetY;
let isResizing = false;
let isRotating = false;
let currentHandle = null;
let initialMouseX, initialMouseY, initialWidth, initialHeight, initialAngle;

let objects = [
{ id: 1, name: "Object 1", x: 50, y: 50, width: 100, height: 100, angle: 0, opacity: 1, layer: 0, color: "#FFC080", behaviours: {} },
{ id: 2, name: "Object 2", x: 250, y: 150, width: 80, height: 80, angle: 45, opacity: 1, layer: 1, color: "#FF9900", behaviours: {} },
{ id: 3, name: "Object 3", x: 500, y: 120, width: 50, height: 60, angle: 80, opacity: 1, layer: 2, color: "#FFD700", behaviours: {} }
];

var selectedObject = null;

function drawObjects() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
objects.sort((a, b) => a.layer - b.layer).forEach(drawObject);
}

function drawObject(obj) {
ctx.save(); // Save the canvas state before any transformations

ctx.globalAlpha = obj.opacity;

// Move the canvas to the center of the object, then rotate
ctx.translate(obj.x, obj.y); // Use top-left as the origin, not center
ctx.translate(obj.width / 2, obj.height / 2); // Shift to the center
ctx.rotate((obj.angle * Math.PI) / 180); // Apply rotation

// Now, draw the object centered on the (0, 0) point (its center)
ctx.fillStyle = obj.color;
ctx.fillRect(-obj.width / 2, -obj.height / 2, obj.width, obj.height);

// Draw the center point for reference
ctx.fillStyle = "black";
ctx.beginPath();
ctx.arc(0, 0, 2, 0, 2 * Math.PI);
ctx.fill();

// Draw the size text
ctx.fillStyle = "black";
ctx.font = "12px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(`${Math.round(obj.width)}x${Math.round(obj.height)}`, 0, obj.height / 2 + 15);

// Restore canvas state after drawing the object
ctx.restore();

// Now for the outline, ensure it's drawn at the correct location, accounting for the transformation
if (obj === selectedObject && isResizing) {
ctx.save(); // Save the current state

ctx.translate(obj.x, obj.y);
ctx.translate(obj.width / 2, obj.height / 2); // Move to center
ctx.rotate((obj.angle * Math.PI) / 180);

// Outline of the object (square outline)
ctx.beginPath();
ctx.moveTo(-obj.width / 2, -obj.height / 2);
ctx.lineTo(obj.width / 2, -obj.height / 2);
ctx.lineTo(obj.width / 2, obj.height / 2);
ctx.lineTo(-obj.width / 2, obj.height / 2);
ctx.closePath();
ctx.stroke();

ctx.restore(); // Restore state for normal drawing behavior
}
}

function updateHandles() {
if (selectedObject) {
const canvasRect = canvas.getBoundingClientRect();
const cx = selectedObject.x + selectedObject.width / 2;
const cy = selectedObject.y + selectedObject.height / 2;
const width = selectedObject.width;
const height = selectedObject.height;
const angle = selectedObject.angle;
const angleRad = angle * Math.PI / 180;

const handles = [
{ id: 'top-left-handle', dx: -width / 2, dy: -height / 2 },
{ id: 'top-right-handle', dx: width / 2, dy: -height / 2 },
{ id: 'bottom-left-handle', dx: -width / 2, dy: height / 2 },
{ id: 'bottom-right-handle', dx: width / 2, dy: height / 2 },
{ id: 'middle-left-handle', dx: -width / 2, dy: 0 },
{ id: 'middle-right-handle', dx: width / 2, dy: 0 },
{ id: 'middle-top-handle', dx: 0, dy: -height / 2 },
{ id: 'middle-bottom-handle', dx: 0, dy: height / 2 },
{ id: 'rotate-top-left-handle', dx: -width / 2 - 10, dy: -height / 2 - 10 },
{ id: 'rotate-top-right-handle', dx: width / 2 + 10, dy: -height / 2 - 10 },
{ id: 'rotate-bottom-left-handle', dx: -width / 2 - 10, dy: height / 2 + 10 },
{ id: 'rotate-bottom-right-handle', dx: width / 2 + 10, dy: height / 2 + 10 }
];

handles.forEach(handle => {
const element = document.getElementById(handle.id);
const rotatedX = handle.dx * Math.cos(angleRad) - handle.dy * Math.sin(angleRad);
const rotatedY = handle.dx * Math.sin(angleRad) + handle.dy * Math.cos(angleRad);
const x = cx + rotatedX;
const y = cy + rotatedY;

element.style.left = `${x + canvasRect.left}px`;
element.style.top = `${y + canvasRect.top}px`;
element.style.display = 'block';
});
}
}

function rotatePoint(x, y, cx, cy, angle) {
const rad = angle * Math.PI / 180;
const cos = Math.cos(rad);
const sin = Math.sin(rad);
return {
x: (x - cx) * cos - (y - cy) * sin + cx,
y: (x - cx) * sin + (y - cy) * cos + cy
};
}
function unrotatePoint(x, y, cx, cy, angle) {
return rotatePoint(x, y, cx, cy, -angle);
}

function getCanvasMousePosition(event) {
const rect = canvas.getBoundingClientRect();
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top
};
}

// SELECT OBJECT
canvas.addEventListener("mousedown", (event) => {
const pos = getCanvasMousePosition(event);

const objectsUnderCursor = objects.filter(obj => {
const centerX = obj.x + obj.width / 2;
const centerY = obj.y + obj.height / 2;

// Transform cursor position relative to object's center and rotation
const dx = pos.x - centerX;
const dy = pos.y - centerY;
const angle = -obj.angle * Math.PI / 180;

const rotatedX = dx * Math.cos(angle) - dy * Math.sin(angle);
const rotatedY = dx * Math.sin(angle) + dy * Math.cos(angle);

// Check if the rotated point is within the object's bounds
const absWidth = Math.abs(obj.width);
const absHeight = Math.abs(obj.height);
return Math.abs(rotatedX) highest.layer ? current : highest;
}, { layer: -999 });

if (selectedObject && selectedObject.layer !== -999) {
updateHandles();

offsetX = pos.x - selectedObject.x;
offsetY = pos.y - selectedObject.y;
isDragging = true;
} else {
selectedObject = null;
// console.log('selectedObject = null');
// Hide handles when no object is selected
const handles = document.querySelectorAll('.transform-handle, .rotate-handle');
handles.forEach(handle => {
handle.style.display = 'none';
});
}
});

canvas.addEventListener("mousemove", (event) => {
if (isDragging && selectedObject) {
const pos = getCanvasMousePosition(event);
selectedObject.x = pos.x - offsetX;
selectedObject.y = pos.y - offsetY;
updateHandles();
}
});

function onHandleMouseDown(event, type) {
event.preventDefault();
event.stopPropagation();

if (!selectedObject) return;

currentHandle = event.target.id;
const pos = getCanvasMousePosition(event);

if (currentHandle.startsWith('rotate-')) {
isRotating = true;
const center = {
x: selectedObject.x + selectedObject.width / 2,
y: selectedObject.y + selectedObject.height / 2
};
initialAngle = Math.atan2(pos.y - center.y, pos.x - center.x) * 180 / Math.PI - selectedObject.angle;
} else {
isResizing = true;
initialMouseX = pos.x;
initialMouseY = pos.y;
initialWidth = selectedObject.width;
initialHeight = selectedObject.height;
initialX = selectedObject.x;
initialY = selectedObject.y;
}
}

// RESIZE
window.addEventListener('mousemove', (event) => {
if (!selectedObject) return;

const pos = getCanvasMousePosition(event);
const center = {
x: selectedObject.x + selectedObject.width / 2,
y: selectedObject.y + selectedObject.height / 2
};

if (isResizing) {
// Convert mouse coordinates to object's local space
const angleRad = -selectedObject.angle * Math.PI / 180;
const dx = pos.x - center.x;
const dy = pos.y - center.y;

// Get rotated mouse position
const rotatedX = dx * Math.cos(angleRad) - dy * Math.sin(angleRad);
const rotatedY = dx * Math.sin(angleRad) + dy * Math.cos(angleRad);

// Get initial rotated position
const initialDx = initialMouseX - center.x;
const initialDy = initialMouseY - center.y;
const initialRotatedX = initialDx * Math.cos(angleRad) - initialDy * Math.sin(angleRad);
const initialRotatedY = initialDx * Math.sin(angleRad) + initialDy * Math.cos(angleRad);

// Calculate deltas in rotated space
const deltaX = rotatedX - initialRotatedX;
const deltaY = rotatedY - initialRotatedY;

let newWidth = selectedObject.width;
let newHeight = selectedObject.height;
let newX = selectedObject.x;
let newY = selectedObject.y;

switch (currentHandle) {
case 'bottom-right-handle':
newWidth = initialWidth + deltaX * 2;
newHeight = initialHeight + deltaY * 2;
break;

case 'bottom-left-handle':
newWidth = initialWidth - deltaX * 2;
newHeight = initialHeight + deltaY * 2;
break;

case 'top-right-handle':
newWidth = initialWidth + deltaX * 2;
newHeight = initialHeight - deltaY * 2;
break;

case 'top-left-handle':
newWidth = initialWidth - deltaX * 2;
newHeight = initialHeight - deltaY * 2;
break;

case 'middle-right-handle':
newWidth = initialWidth + deltaX * 2;
break;

case 'middle-left-handle':
newWidth = initialWidth - deltaX * 2;
break;

case 'middle-top-handle':
newHeight = initialHeight - deltaY * 2;
break;

case 'middle-bottom-handle':
newHeight = initialHeight + deltaY * 2;
break;
}

// Calculate new center position
const widthDiff = newWidth - initialWidth;
const heightDiff = newHeight - initialHeight;

// Update the object's position to maintain its center
newX = center.x - newWidth / 2;
newY = center.y - newHeight / 2;

// Apply the changes
selectedObject.width = newWidth;
selectedObject.height = newHeight;
selectedObject.x = newX;
selectedObject.y = newY;

} else if (isRotating) {
const angle = Math.atan2(pos.y - center.y, pos.x - center.x) * 180 / Math.PI - initialAngle;
selectedObject.angle = angle;
}

updateHandles();
});

window.addEventListener('mouseup', () => {
isDragging = false;
isResizing = false;
isRotating = false;
});

const handles = document.querySelectorAll('.transform-handle, .rotate-handle');
handles.forEach(handle => {
handle.addEventListener('mousedown', (event) => onHandleMouseDown(event, handle.id === 'rotate-handle' ? 'rotate' : 'resize'));
// Initially hide handles
handle.style.display = 'none';
});

function editorLoop() {
drawObjects();
if (selectedObject) {
handles.forEach(handle => handle.style.display = 'block');
updateHandles();
}
requestAnimationFrame(editorLoop);
}

editorLoop();



< /code>

Что я попробовал:

реализовано изменение размера с использованием ручек:

Я добавил несколько ручек изменения размера вокруг Объект.
Каждая ручка регулирует ширину и высоту при перетаскивании.

Обработанное вращение отдельно:

вращение применяется с использованием дополнительных расположенных ручек. Вокруг объекта.
Угол обновляется на основе положения курсора относительно центра объекта. Я попытался обновить ширину и высоту на основе движения мыши. Br />
Я использовал тригонометрические функции для учета вращения.
Проблема сохраняется - ручки не ведут себя так, как ожидалось при изменении размера.
< /p>
поддерживается объект. Состояние выбора:

Только выбранное объект должен отображать ручки Restraze /rotate.
Нажмите на улицу, должен отказаться от объекта.

Несмотря на эти попытки, я Я изо всех сил пытаюсь правильно произойти с изменяемого размера от перетаскиваемого края, а не изменять размер объекта симметрично из центра.

Подробнее здесь: https://stackoverflow.com/questions/794 ... side-fixed
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «Javascript»