【问题标题】:How do I rotate, scale and translate on Html5 Canvas?如何在 Html5 Canvas 上旋转、缩放和翻译?
【发布时间】:2021-06-10 07:32:12
【问题描述】:

过去几天我尝试在画布上旋转、缩放和平移形状,但没有取得太大的成功。 我已经阅读了我在互联网上可以找到的关于类似问题的所有内容,但我似乎仍然无法适应我自己的问题。

如果所有东西都按相同的比例绘制,我仍然可以拖放。如果我旋转形状,那么 mouseOver 就会搞砸,因为世界坐标不再与形状坐标对应。 如果我缩放,那么就不可能选择任何形状。 我看我的代码,不明白我做错了什么。

我阅读了一些针对类似问题的非常好的和详细的 stackoverflow 解决方案。 例如,用户@blindman67 建议使用setTransform 助手和getMouseLocal 助手来获取鼠标相对于变换后形状的坐标。

inverse transform matrix

我花了一些时间来解决我的问题。 这是我尝试过的一个例子。任何建议表示赞赏。

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


    【解决方案1】:

    如果我明天有时间,我将尝试在您的代码中实现以下内容,但我可以为您提供一个工作示例,说明如何在旋转的矩形上获得鼠标碰撞精度。我对此也有同样的挣扎,最后找到了一个很好的解释和代码,我可以开始工作。看看这个website

    现在对于我自己的实现,我没有使用该网站上的方法来获取我的顶点。正如您将在我的代码中看到的,我的Square 类中有一个名为updateCorners() 的函数。我还有名为 this.tl.xthis.tl.y 的对象(每个角)。

    公式是我用来获取平移和旋转矩形的顶点的公式,角对象是用来确定碰撞的公式。从那里我使用了distance() 函数(勾股定理)、triangleArea() 函数,然后是我重命名为collision()clickHit() 函数并进行了一些更改。

    下面sn-p中的例子

    let canvas = document.getElementById("canvas");
    let ctx = canvas.getContext("2d");
    canvas.width = 400;
    canvas.height = 400;
    let shapes = [];
    let mouse = {
      x: null,
      y: null
    }
    canvas.addEventListener('mousemove', e => {
      mouse.x = e.x - canvas.getBoundingClientRect().x;
      mouse.y = e.y - canvas.getBoundingClientRect().y;
      
    })
    
    class Square {
      constructor(x, y, w, h, c) {
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        this.c = c;
        this.a = 0;
        this.r = this.a * (Math.PI/180);
        this.cx = this.x + this.w/2;
        this.cy = this.y + this.h/2;
        //used to track corners
        this.tl = {x: 0, y: 0};
        this.tr = {x: 0, y: 0};
        this.br = {x: 0, y: 0};
        this.bl = {x: 0, y: 0};
      }
      draw() {
        ctx.save();
        ctx.translate(this.x, this.y)
        ctx.rotate(this.r);
        ctx.fillStyle = this.c;
        ctx.fillRect(-this.w/2,-this.h/2,this.w,this.h);
        ctx.restore();
      }
      updateCorners() {
        this.a += 0.1
        this.r = this.a * (Math.PI/180);
        let cos = Math.cos(this.r);
        let sin = Math.sin(this.r)
        //updates Top Left Corner
        this.tl.x = (this.x-this.cx)*cos - (this.y-this.cy)*sin+(this.cx-this.w/2);
        this.tl.y = (this.x-this.cx)*sin + (this.y-this.cy)*cos+(this.cy-this.h/2)
        //updates Top Right Corner
        this.tr.x = ((this.x+this.w)-this.cx)*cos - (this.y-this.cy)*sin+(this.cx-this.w/2)
        this.tr.y = ((this.x+this.w)-this.cx)*sin + (this.y-this.cy)*cos+(this.cy-this.h/2)
        //updates Bottom Right Corner
        this.br.x = ((this.x+this.w)-this.cx)*cos - ((this.y+this.h)-this.cy)*sin+(this.cx-this.w/2)
        this.br.y = ((this.x+this.w)-this.cx)*sin + ((this.y+this.h)-this.cy)*cos+(this.cy-this.h/2)
        //updates Bottom Left Corner
        this.bl.x = (this.x-this.cx)*cos - ((this.y+this.h)-this.cy)*sin+(this.cx-this.w/2)
        this.bl.y = (this.x-this.cx)*sin + ((this.y+this.h)-this.cy)*cos+(this.cy-this.h/2)
      }
    }
    let square1 = shapes.push(new Square(250, 70, 25, 25, 'red'));
    let square2 = shapes.push(new Square(175,210, 100, 50, 'blue'));
    let square3 = shapes.push(new Square(50,100, 30, 50, 'purple'));
    let square4 = shapes.push(new Square(140,120, 120, 20, 'pink'));
    
    //https://joshuawoehlke.com/detecting-clicks-rotated-rectangles/
    //pythagorean theorm using built in javascript hypot
    function distance(p1, p2) {
        return Math.hypot(p1.x-p2.x, p1.y-p2.y);
    }
    
    //Heron's formula used to determine area of triangle
    //in the collision() function we will break the rectangle into triangles
    function triangleArea(d1, d2, d3) {
        var s = (d1 + d2 + d3) / 2;
        return Math.sqrt(s * (s - d1) * (s - d2) * (s - d3));
    }
    
    function collision(mouse, rect) {
      //area of rectangle
        var rectArea = Math.round(rect.w * rect.h);
        // Create an array of the areas of the four triangles
        var triArea = [
            // mouse posit checked against tl-tr
            triangleArea(
                distance(mouse, rect.tl),
                distance(rect.tl, rect.tr),
                distance(rect.tr, mouse)
            ),
            // mouse posit checked against tr-br
            triangleArea(
                distance(mouse, rect.tr),
                distance(rect.tr, rect.br),
                distance(rect.br, mouse)
            ),
            // mouse posit checked against tr-bl
            triangleArea(
                distance(mouse, rect.br),
                distance(rect.br, rect.bl),
                distance(rect.bl, mouse)
            ),
            // mouse posit checked against bl-tl
            triangleArea(
                distance(mouse, rect.bl),
                distance(rect.bl, rect.tl),
                distance(rect.tl, mouse)
            )
        ];
        let triArea2 = Math.round(triArea.reduce(function(a,b) { return a + b; }, 0));
        if (triArea2 > rectArea) {
            return false;
        }
        return true;
    }
    
    
    function animate() {
      ctx.clearRect(0,0,canvas.width,canvas.height);
      ctx.fillStyle = 'black';
      ctx.fillText('x: '+mouse.x+',y: '+mouse.y, 50, 50);
      for (let i=0; i< shapes.length; i++) {
        shapes[i].draw();
        shapes[i].updateCorners();
        if (collision(mouse, shapes[i])) {
        shapes[i].c = 'red';
      } else {
        shapes[i].c = 'green'
      }
      }
      requestAnimationFrame(animate)
    }
    animate();
    &lt;canvas id="canvas"&gt;&lt;/canvas&gt;

    我确信还有很多其他方法可以做到这一点,但这是我能够理解并开始工作的方法。我并没有真正搞砸规模,所以我帮不上什么忙。

    更新: 这是使用您想要的方法的sn-p。现在您可以旋转、缩放和平移,并且仍然可以在形状内部单击。请注意,我将您的鼠标更改为全局鼠标对象,使其成为每个鼠标...函数中的变量。

    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 localMouse = { x: null, y: null };
    let mouse = { x: null, y: null };
    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();
      mouse.x = e.clientX - offsetX;
      mouse.y = e.clientY - offsetY;
      startX = mouse.x;
      startY = mouse.y;
      mouseIsDown = true;
      selectedShape = "";
      if (selectedTool === "SELECT") {
        for (const shapeId in shapes) {
          const shape = shapes[shapeId];
          if (shape.mouseIsOver()) {
            selectedShape = shape.id;
            shapes[shape.id].isSelected = true;
          } else {
            shapes[shape.id].isSelected = false;
          }
        }
      }
      draw();
    }
    
    function canvasMouseUp(e) {
       e.preventDefault();
      mouse.x = e.clientX - offsetX;
      mouse.y = e.clientY - offsetY;
      endX = mouse.x;
      endY = mouse.y;
      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();
      mouse.x = e.clientX - offsetX;
      mouse.y = 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(mouse.x, mouse.y);
        }
      }
    }
    
    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 = { x: 1, y: 1 };
      }
    
      drawShape(ctx) {
        switch (this.type) {
          case "rectangle":
            this._drawRectangle(ctx);
            break;
        }
      }
    
      _drawRectangle(ctx) {
        ctx.save();
        setTransform(
          this.x + this.width / 2,
          this.y + this.height / 2,
          this.scale.x,
          this.scale.y,
          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() {
        localMouse = getMouseLocal(
          mouse.x,
          mouse.y,
          this.x + this.width / 2,
          this.y + this.height / 2,
          this.scale.x,
          this.scale.y,
          degreesToRadians(this.rotation)
        );
        if (this.type === "rectangle") {
          if (
            localMouse.x > 0 - this.width / 2 &&
            localMouse.x < 0 + this.width / 2 &&
            localMouse.y < 0 + this.height / 2 &&
            localMouse.y > 0 - this.height / 2
          ) {
            return true;
          }
        }
      }
    }
    
    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.x = scale;
        shape.scale.y = scale;
      }
      draw();
    }
    
    function setTransform(x, y, sx, sy, rotate) {
      var xdx = Math.cos(rotate); // create the x axis
      var xdy = Math.sin(rotate);
      ctx.setTransform(xdx * sx, xdy * sx, -xdy * sy, xdx * sy, x, y);
    }
    
    function getMouseLocal(mouseX, mouseY, x, y, sx, sy, rotate) {
      var xdx = Math.cos(rotate); // create the x axis
      var xdy = Math.sin(rotate);
      var cross = xdx * sx * xdx * sy - xdy * sx * -xdy * sy;
      var ixdx = (xdx * sy) / cross; // create inverted x axis
      var ixdy = (-xdy * sx) / cross;
      var iydx = (xdy * sy) / cross; // create inverted y axis
      var iydy = (xdx * sx) / cross;
    
      mouseX -= x;
      mouseY -= y;
    
      var localMouseX = mouseX * ixdx + mouseY * iydx;
      var localMouseY = mouseX * ixdy + mouseY * iydy;
    
      return { x: localMouseX, y: localMouseY };
    }
    
    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>
      <button id="triangle" data-tool="TRIANGLE">triangle</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>

    【讨论】:

    • 谢谢贾斯汀,非常有趣的方法。我会尝试实现它,看看它是否对我的情况有帮助。我遇到的问题是当我有很多对象时,每个对象都以不同的角度旋转。不是矩形的形状呢?
    • 对于许多矩形来说,这很容易解决。我更新了sn-p。在我有时间坐下来玩它之后,我将不得不就奇怪的形状回复你。我猜这也只是将它们切成三角形的问题。它可能需要向碰撞函数添加边数并在每边的两个顶点和鼠标位置之间运行triangleArea。这个周末我会玩它,看看我能不能得到一些有用的东西。无论您采用哪种解决方案,我都会通知您。
    • 也许您可以通过向您展示我正在开发的“应用程序”来了解我的问题的更多背景信息。我提交的那个只是简化了,因为里面有很多不必要的代码。如果你去这里,画一个形状,然后从侧边栏旋转它,你会明白我的意思irosgrim.github.io/draw
    • 我让它工作并用第二个 sn-p 更新了答案。我尝试了几个矩形,没有发现任何问题。我添加了一个三角形按钮,但还没有这样做,因为我想为您提供一个工作示例。
    • 哇,贾斯汀。惊人的工作。谢谢,谢谢!
    猜你喜欢
    • 2022-01-10
    • 2017-06-05
    • 2016-06-27
    • 2012-03-22
    • 1970-01-01
    • 2022-01-04
    • 1970-01-01
    • 2012-10-27
    • 1970-01-01
    相关资源
    最近更新 更多