【发布时间】:2021-06-10 07:32:12
【问题描述】:
过去几天我尝试在画布上旋转、缩放和平移形状,但没有取得太大的成功。 我已经阅读了我在互联网上可以找到的关于类似问题的所有内容,但我似乎仍然无法适应我自己的问题。
如果所有东西都按相同的比例绘制,我仍然可以拖放。如果我旋转形状,那么 mouseOver 就会搞砸,因为世界坐标不再与形状坐标对应。
如果我缩放,那么就不可能选择任何形状。
我看我的代码,不明白我做错了什么。
我阅读了一些针对类似问题的非常好的和详细的 stackoverflow 解决方案。
例如,用户@blindman67 建议使用setTransform 助手和getMouseLocal 助手来获取鼠标相对于变换后形状的坐标。
我花了一些时间来解决我的问题。 这是我尝试过的一个例子。任何建议表示赞赏。
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth - 40;
canvas.height = window.innerHeight - 60;
const canvasBounding = canvas.getBoundingClientRect();
const offsetX = canvasBounding.left;
const offsetY = canvasBounding.top;
let scale = 1;
let selectedShape = '';
let startX = 0;
let startY = 0;
let endX = 0;
let endY = 0;
let mouseIsDown = false;
let mouseIsMovingShape = false;
let selectedTool = 'SELECT';
let shapes = {};
const selectButton = document.getElementById('select');
const rectangleButton = document.getElementById('rectangle');
canvas.addEventListener('mousedown', canvasMouseDown);
canvas.addEventListener('mouseup', canvasMouseUp);
canvas.addEventListener('mousemove', canvasMouseMove);
function canvasMouseDown(e) {
e.preventDefault();
const mouseX = e.clientX - offsetX;
const mouseY = e.clientY - offsetY;
startX = mouseX;
startY = mouseY;
mouseIsDown = true;
selectedShape = '';
if (selectedTool === 'SELECT') {
for (const shapeId in shapes) {
const shape = shapes[shapeId];
if (shape.mouseIsOver(mouseX, mouseY)) {
selectedShape = shape.id;
shapes[shape.id].isSelected = true;
} else {
shapes[shape.id].isSelected = false;
}
}
}
draw();
}
function canvasMouseUp(e) {
e.preventDefault();
const mouseX = e.clientX - offsetX;
const mouseY = e.clientY - offsetY;
endX = mouseX;
endY = mouseY;
mouseIsDown = false;
const tooSmallShape = Math.abs(endX) - startX < 1 || Math.abs(endY) - startY < 1;
if (tooSmallShape) {
return;
}
if (selectedTool === 'RECTANGLE') {
const newShape = new Shape(selectedTool.toLowerCase(), startX, startY, endX, endY);
shapes[newShape.id] = newShape;
selectedShape = '';
setActiveTool('SELECT');
}
draw();
}
function canvasMouseMove(e) {
e.preventDefault();
const mouseX = e.clientX - offsetX;
const mouseY = e.clientY - offsetY;
const dx = e.movementX;
const dy = e.movementY;
if (mouseIsDown) {
draw();
if (selectedTool === 'SELECT' && selectedShape !== '') {
const shape = shapes[selectedShape];
shape.x += dx;
shape.y += dy;
}
if (selectedTool === 'RECTANGLE') {
drawShapeGhost(mouseX, mouseY);
}
}
}
function draw() {
clear();
for (const shapeId in shapes) {
const shape = shapes[shapeId];
shape.drawShape(ctx);
}
}
function clear() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
ctx.fillRect(0, 0, canvas.width, canvas.height)
}
function drawShapeGhost(x, y) {
ctx.strokeStyle = 'rgba(0, 0, 0, 0.5)';
ctx.strokeRect(startX, startY, x - startX, y - startY);
ctx.stroke();
}
function setActiveTool(tool) {
selectedTool = tool;
if (tool === 'RECTANGLE') {
rectangleButton.classList.add('active');
selectButton.classList.remove('active');
selectedTool = tool;
}
if (tool === 'SELECT') {
rectangleButton.classList.remove('active');
selectButton.classList.add('active');
selectedTool = tool;
}
}
function degreesToRadians(degrees) {
return (Math.PI * degrees) / 180;
};
class Shape {
constructor(shapeType, startX, startY, endX, endY, fill, stroke) {
this.id = shapeType + Date.now();
this.type = shapeType;
this.x = startX;
this.y = startY;
this.width = Math.abs(endX - startX);
this.height = Math.abs(endY - startY);
this.fill = fill || 'rgba(149, 160, 178, 0.8)';
this.stroke = stroke || 'rgba(0, 0, 0, 0.8)';
this.rotation = 0;
this.isSelected = false;
this.scale = 1;
}
drawShape(ctx) {
switch (this.type) {
case 'rectangle':
this._drawRectangle(ctx);
break;
}
}
_drawRectangle(ctx) {
ctx.save();
ctx.scale(this.scale, this.scale);
ctx.translate(this.x + this.width / 2, this.y + this.height / 2);
ctx.rotate(degreesToRadians(this.rotation));
if (this.fill) {
ctx.fillStyle = this.fill;
ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
}
if (this.stroke !== null) {
ctx.strokeStyle = this.stroke;
ctx.strokeWidth = 1;
ctx.strokeRect(-this.width / 2, -this.height / 2, this.width, this.height);
ctx.stroke();
}
if (this.isSelected) {
ctx.strokeStyle = 'rgba(254, 0, 0, 1)';
ctx.strokeWidth = 1;
ctx.strokeRect(-this.width / 2, -this.height / 2, this.width, this.height)
ctx.stroke();
ctx.closePath();
}
ctx.restore();
}
mouseIsOver(mouseX, mouseY) {
if (this.type === 'rectangle') {
return (mouseX > this.x && mouseX < this.x + this.width && mouseY > this.y && mouseY < this.y + this.height);
}
}
}
const menu = document.getElementById('menu');
const rotation = document.getElementById('rotation');
const scaleSlider = document.getElementById('scale');
menu.addEventListener('click', onMenuClick);
rotation.addEventListener('input', onRotationChange);
scaleSlider.addEventListener('input', onScaleChange);
function onMenuClick(e) {
const tool = e.target.dataset.tool;
if (tool && tool === 'RECTANGLE') {
rectangleButton.classList.add('active');
selectButton.classList.remove('active');
selectedTool = tool;
}
if (tool && tool === 'SELECT') {
rectangleButton.classList.remove('active');
selectButton.classList.add('active');
selectedTool = tool;
}
}
function onRotationChange(e) {
if (selectedShape !== '') {
shapes[selectedShape].rotation = e.target.value;
draw();
}
}
function onScaleChange(e) {
scale = e.target.value;
for (const shapeId in shapes) {
const shape = shapes[shapeId];
shape.scale = scale;
}
draw();
}
function setTransform(ctx, x, y, scaleX, scaleY, rotation) {
const xDx = Math.cos(rotation);
const xDy = Math.sin(rotation);
ctx.setTransform(xDx * scaleX, xDy * scaleX, -xDy * scaleY, xDx * scaleY, x, y);
}
function getMouseLocal(mouseX, mouseY, x, y, scaleX, scaleY, rotation) {
const xDx = Math.cos(rotation);
const xDy = Math.sin(rotation);
const cross = xDx * scaleX * xDx * scaleY - xDy * scaleX * (-xDy) * scaleY;
const ixDx = (xDx * scaleY) / cross;
const ixDy = (-xDy * scaleX) / cross;
const iyDx = (xDy * scaleY) / cross;
const iyDy = (xDx * scaleX) / cross;
mouseX -= x;
mouseY -= y;
const localMouseX = mouseX * ixDx + mouseY * iyDx;
const localMouseY = mouseX * ixDy + mouseY * iyDy;
return {
x: localMouseX,
y: localMouseY,
}
}
function degreesToRadians(degrees) {
return (Math.PI * degrees) / 180
};
function radiansToDegrees(radians) {
return radians * 180 / Math.PI
};
let timer;
function debounce(fn, ms) {
clearTimeout(timer);
timer = setTimeout(() => fn(), ms);
}
canvas {
margin-top: 1rem;
border: 1px solid black;
}
button {
border: 1px solid #adadad;
background-color: transparent;
}
.active {
background-color: lightblue;
}
.menu {
display: flex;
}
.d-flex {
display: flex;
margin-left: 2rem;
}
.rotation input {
margin-left: 1rem;
max-width: 50px;
}
<div id="menu" class="menu">
<button id="select" data-tool="SELECT">select</button>
<button id="rectangle" data-tool="RECTANGLE">rectangle</button>
<div class="d-flex">
<label for="rotation">rotation </label>
<input type="number" id="rotation" value="0">
</div>
<div class="d-flex">
<label for="scale">scale </label>
<input type="range" step="0.1" min="0.1" max="10" value="1" name="scale" id="scale">
</div>
</div>
<canvas id="canvas"></canvas>
【问题讨论】:
标签: javascript canvas transform