Некоторые основные и второстепенные проблемы
- ВАЖНО Когда вы вызываете
ctx.save
, вы помещаете sh текущее состояние 2D холста в стек. Если вы не позвоните ctx.restore
, бессмысленно звонить ctx.save
. Хуже всего то, что каждый вызов для сохранения жует память. window
- это глобальное выражение this. Вам не нужно использовать это. Например, window.devicePixelRatio
совпадает с devicePixelRatio
- Вам не нужно использовать
setAttribute
. Используйте setAttribute
только в том случае, если вы хотите, чтобы разметка отражала состояние свойств, а свойство не определено DOM. - Когда вы устанавливаете холст
width
или height
, холст очищается и состояние сбрасывается по умолчанию. Нет необходимости очищать холст после установки его размера и сохранять его состояние. - Упростить математику. У вас есть что-то вроде
(a / 2) - (b / 2) * c
при расчете происхождения. Он имеет общий делитель 2
и, таким образом, становится (a - b) * c / 2
Система координат
Использование общей системы координат и преобразование при рендеринге на любой холст. Поскольку оба холста отрисовывают одно и то же изображение (и оно показывает, что это изображение является соответствующей системой координат), используйте систему координат изображения.
У вас есть 4 системы координат: изображение, масштабированное изображение canvas1, масштабированное image canvas2 и rect
в виде canvas1 пикселей. Проблема в том, что вы преобразуете из пиксельных координат canvas1 (rect
) в координаты изображения canvas2
Чтобы исправить масштабирование и позиционирование, преобразуйте пиксельные координаты canvas1 в координаты изображения (см. Пример функции mouseEvents
). Затем в вашей функции рендеринга преобразуйте координаты изображения обратно в пиксельные координаты холста (см. Пример функции updateCanvas
).
Пример
Я удалил много ненужного и повторного кода, чтобы уменьшить сложность и сделать код более читабельным и понятным. Использовал современный JS и добавил некоторый пользовательский интерфейс
- Одна функция для настройки холста
- Одна функция для обработки событий мыши
- Одна функция для рендеринга прямоугольник
- Оставьте отзыв, используя курсор "перекрестие" и "нет"
Для примера я удалил кнопку (не имеет отношения к вопросу), уменьшил размер двух холстов, чтобы соответствовать очень маленькое окно с фрагментом, и второй прямоугольник обновляется в реальном времени при построении первого прямоугольника.
Обратите внимание, что rect
находится в координатах изображения.
const COLOR1 = "#F00", COLOR2 = "#FF0", LINE_WIDTH = 2;
const ctx1 = canvas1.getContext("2d"), ctx2 = canvas2.getContext("2d");
const img = new Image;
const rect = {};
var drag = false;
img.src = "https://i.imgur.com/1n8sbrF.jpg";
img.addEventListener("load",() => {
setupCanvas(ctx1);
setupCanvas(ctx2);
canvas1.addEventListener("mousedown", mouseEvent);
canvas1.addEventListener("mouseup", mouseEvent);
canvas1.addEventListener("mousemove", mouseEvent);
}, {once: true});
function setupCanvas(ctx, coords = {}) {
const dPR = devicePixelRatio || 1;
const w = ctx.canvas.width = ctx.canvas.clientWidth * dPR;
const h = ctx.canvas.height = ctx.canvas.clientHeight * dPR;
const scale = coords.scale = Math.max(w / img.width, h / img.height);
const x = coords.x = (w - img.width * scale) / 2;
const y = coords.y = (h - img.height * scale) / 2;
ctx.drawImage(img, x, y, img.width * scale, img.height * scale);
ctx.canvas.coords = coords;
}
function mouseEvent(e) {
var cursor = "crosshair";
const co = canvas1.coords;
const x = (e.clientX - this.offsetLeft - co.x) / co.scale;
const y = (e.clientY - this.offsetTop - co.y) / co.scale;
if (e.type === "mousedown") {
rect.x = x;
rect.y = y;
drag = true;
canvas1.title = "";
} else if (e.type === "mouseup") { drag = false }
if (drag) {
cursor = "none";
rect.w = x - rect.x;
rect.h = y - rect.y;
updateCanvas(ctx1, COLOR1, co);
updateCanvas(ctx2, COLOR2, canvas2.coords);
}
canvas1.style.cursor = cursor;
}
function updateCanvas(ctx, color, {x, y, scale}) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.drawImage(img, x, y, img.width * scale, img.height * scale);
ctx.lineWidth = LINE_WIDTH;
ctx.strokeStyle = color;
ctx.strokeRect(x + rect.x * scale, y + rect.y * scale, rect.w * scale, rect.h * scale);
}
html, body{
width: 90%;
height: 90%;
}
#div1 {
margin: 10px;
width: 200px;
height: 300px;
border: 2px solid red;
}
#div2 {
position: absolute;
top: 10px;
left: 240px;
width: 300px;
height: 200px;
border: 2px solid #FF0;
}
canvas {
width: 100%;
height: 100%;
}
<div id="div1">
<canvas id="canvas1" title="Click and drag to select area."></canvas>
</div>
<div id="div2">
<canvas id="canvas2"></canvas>
</div>