简体   繁体   中英

Resize rotated shape on HTML5 Canvas

I want to resize a rotated shape without it drifting away. I'm aware that I need to adjust the shape coordinates depending on the rotation angle, but I can't seem to figure out the formula to do it.

This is what I've managed to do so far ( https://jsfiddle.net/9o3vefym/1/ )

<!DOCTYPE html>
<html>
  <body style="margin: 0">
    <canvas
      id="canvas"
      width="400"
      height="400"
      style="border: 1px solid #d3d3d3"
    ></canvas>
    <script>
      const canvas = document.getElementById("canvas");
      const context = canvas.getContext("2d");
      const rect = {
        x: 100,
        y: 100,
        width: 50,
        height: 50,
        angle: 0
      };
      window.onkeydown = keyDown;

      draw();

      function keyDown(evt) {
        if (evt.key == "ArrowRight") {
          rect.width += 5;
        }
        if (evt.key == "ArrowLeft") {
          rect.x -= 5;
          rect.width += 5;
        }
        if (evt.key == "ArrowUp") {
          rect.y -= 5;
          rect.height += 5;
        }
        if (evt.key == "ArrowDown") {
          rect.height += 5;
        }
        if (evt.key == "a") {
          rect.angle -= (45 * Math.PI) / 180;
        }
        if (evt.key == "s") {
          rect.angle += (45 * Math.PI) / 180;
        }
        draw();
      }

      function draw() {
        context.setTransform(1, 0, 0, 1, 0, 0);
        context.clearRect(0, 0, canvas.width, canvas.height);

        const cx = (rect.x * 2 + rect.width) / 2;
        const cy = (rect.y * 2 + rect.height) / 2;
        context.translate(cx, cy);
        context.rotate(rect.angle);
        context.translate(-cx, -cy);

        context.fillRect(rect.x, rect.y, rect.width, rect.height);
      }
    </script>
  </body>
</html>

It works fine if the shape isn't rotated, but it breaks down if I try to resize a rotated shape.

For example, If I rotate the rectangle by pressing the key "a" or "s" and then resize it using any of the arrow keys, the rectangle will "drift away". What I'm trying to do is to keep the rectangle still while resizing it, as it happens when it isn't rotated.

Also, I'm trying to find a solution that doesn't involve changing draw() , since I can't do that in the codebase I'm trying to fix this problem.

I'd really appreciate some help.

This is not fixable outside of draw() . The problem is that the draw method always assumes the center of the rectangle is the rotation point, but what you want is to be able to shift the point of rotation based on which direction the rectangle is growing in. The only way to avoid drift WITHOUT changing draw() is to always keep the rectangle centered at the same point. See the commented portions of the code snippet.

The reason you can't fix it outside of draw() is because of the transform reset done on the first line. Otherwise, you'd be able to adjust the transform before and after draw() to accommodate.

 const canvas = document.getElementById("canvas"); const context = canvas.getContext("2d"); const rect = { x: 100, y: 100, width: 50, height: 50, angle: 0 }; window.onkeydown = keyDown; draw(); function keyDown(evt) { if (evt.key == "ArrowRight") { //rect.x -= 2.5; rect.width += 5; } if (evt.key == "ArrowLeft") { rect.x -= 5; //2.5; rect.width += 5; } if (evt.key == "ArrowUp") { //rect.y -= 2.5; rect.height += 5; } if (evt.key == "ArrowDown") { rect.y -= 5; //2.5; rect.height += 5; } if (evt.key == "a") { rect.angle -= (45 * Math.PI) / 180; } if (evt.key == "s") { rect.angle += (45 * Math.PI) / 180; } draw(); } function draw() { // The next line is why you can't fix the drift outside of draw(). context.setTransform(1, 0, 0, 1, 0, 0); context.clearRect(0, 0, canvas.width, canvas.height); const cx = (rect.x * 2 + rect.width) / 2; const cy = (rect.y * 2 + rect.height) / 2; context.translate(cx, cy); context.rotate(rect.angle); context.translate(-cx, -cy); context.fillStyle = "#000"; context.fillRect(rect.x, rect.y, rect.width, rect.height); context.fillStyle = "#F00"; context.fillRect(cx, cy, 2, 2); }
 <!DOCTYPE html> <html> <body style="margin: 0"> <canvas id="canvas" width="400" height="400" style="border: 1px solid #d3d3d3" ></canvas> <script> </script> </body> </html>

Here's another snippet with a solution that requires changes inside of draw() . The idea is that you define cx and cy first and keep them fixed.

 const canvas = document.getElementById("canvas"); const context = canvas.getContext("2d"); const rect = { x: 100, y: 100, width: 50, height: 50, angle: 0 }; rect.cx = rect.x + rect.width / 2; rect.cy = rect.y + rect.height / 2; window.onkeydown = keyDown; draw(); function keyDown(evt) { if (evt.key == "ArrowRight") { rect.width += 5; } if (evt.key == "ArrowLeft") { rect.x -= 5; rect.width += 5; } if (evt.key == "ArrowUp") { rect.y -= 5; rect.height += 5; } if (evt.key == "ArrowDown") { rect.height += 5; } if (evt.key == "a") { rect.angle -= (45 * Math.PI) / 180; } if (evt.key == "s") { rect.angle += (45 * Math.PI) / 180; } draw(); } function draw() { context.setTransform(1, 0, 0, 1, 0, 0); context.clearRect(0, 0, canvas.width, canvas.height); context.translate(rect.cx, rect.cy); context.rotate(rect.angle); context.translate(-rect.cx, -rect.cy); context.fillStyle = "#000"; context.fillRect(rect.x, rect.y, rect.width, rect.height); context.fillStyle = "#F00"; context.fillRect(rect.cx, rect.cy, 2, 2); }
 <!DOCTYPE html> <html> <body style="margin: 0"> <canvas id="canvas" width="400" height="400" style="border: 1px solid #d3d3d3" ></canvas> <script> </script> </body> </html>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM