调整宽度时如何防止旋转的矩形移动?

How to prevent a rotated rectangle from moving when adjusting its width?

我有一个围绕其中心旋转的矩形,我正在尝试使其变宽。然而,因为它绕着它的中心旋转,使它变宽也在视觉上“移动”了矩形。

我正在寻找的是一种使其变宽然后相应地调整其 x 和 y 分量以使其在视觉上不移动的方法。我在这里创建了问题的重现,所以你可以看到问题:

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");

function rotateCanvas(x, y, a) {
  ctx.translate(x, y);
  ctx.rotate(a);
  ctx.translate(-x, -y);
}

function drawRotateRectangle(x, y, w, h, a) {
  rotateCanvas(x + w / 2, y + h / 2, a);
  ctx.fillRect(x, y, w, h);
  rotateCanvas(x + w / 2, y + h / 2, -a);
}

let which = true;

function render() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  const rWidth = which ? 100 : 200;

  drawRotateRectangle(30, 30, rWidth, 30, 0.5);

  which = !which;
}

render();

setInterval(render, 1000);
<canvas></canvas>

如您所见,当它变宽时,它也会向上移动。实际上,我希望矩形的左上角在调整大小前后位于同一点。

不过,我还希望它仍然绕着它的中心旋转。也就是说,我正在寻找在调整大小发生之前和之后必须将矩形移动多少(更改其 x 和 y 坐标),才能有效地将其锚定到同一位置。

这可能吗?如果是这样,您将如何计算必须修改矩形的 xy 的值,以便在视觉上将其锁定到当前位置?

我知道如果我将矩形围绕其左上角而不是其中心旋转,则不会发生此问题,但出于我的应用目的,矩形 到围绕其中心旋转,因此我正在寻找我必须修改多少 xy 组件才能保持其视觉位置。

因此,这个问题的答案不应涉及以任何方式修改 rotateCanvasdrawRotateRectangle 函数。

我正在寻找的效果示例:

沿旋转轴移动。旋转为 'r',距离为 'd'。假设变换是均匀的(即 x、y 比例在这种情况 1 上相同并且没有倾斜)

沿 x 轴移动 dx 并沿 y 轴移动 dy 的示例

var r = ?; // rotation in radians
var x = ?, y = ?;  // in pixels. coordinate to move
var dx = ?; // distance in pixels along x axis
var dy = ?; // distance in pixels along y axis

const ax = Math.cos(r);
const ay = Math.sin(r);
x += dx * ax - dy * ay
y += dx * ay + dy * ax;

演示

展开一个矩形。函数rect.expand(left, right, top, bottom)改变矩形的大小调整原点以保持原边不变。

const ctx = canvas.getContext("2d");
requestAnimationFrame(renderLoop);
const W = canvas.width, H = canvas.height;
var expanding = 1, expandCount = 0; 
const rect = {
    x: W / 2, 
    y: H / 2,
    r: 0,  
    w: 100,
    h: 50,
    expand(left, right, top, bottom) {
        const ax = Math.cos(this.r) * 0.5;
        const ay = Math.sin(this.r) * 0.5;
        this.w += left + right;
        this.h += top + bottom;
        this.x += -left * ax + right * ax + top * ay - bottom * ay;
        this.y += -left * ay + right * ay - top * ax + bottom * ax;
    },
    draw() {
        const ax = Math.cos(this.r);
        const ay = Math.sin(this.r);
        ctx.setTransform(ax, ay, -ay, ax, this.x, this.y);
        ctx.strokeRect(-this.w * 0.5, -this.h * 0.5, this.w, this.h);
    }
};

function renderLoop() {
    ctx.setTransform(1,0,0,1,0,0);
    ctx.clearRect(0, 0, W, H);
    if (expanding === 1) {
        expandCount += 1;
        expandCount === 50 && (expanding = -1);
    } else if (expanding === -1) {
        expandCount -= 1;
        expandCount === 0 && (expanding = 1);
    }
    
    rect.r += 0.01;
    rect.expand(0, expanding, 0, 0);

    rect.draw();
    requestAnimationFrame(renderLoop);
}


 
canvas {
    border: 1px solid black;
}
   <canvas id = "canvas" width="200" height="200"></canvas>

其他答案很好很优雅,但你说你想保留 rotateCanvasdrawRotateRectangle,对吧?

我们可以使用一些 matrix transformations on a DOMPoint:

来模仿您的 rotateCanvas 函数中的转换
  const x = 60;
  const y = 30;
  const h = 30;
  const a = 0.5;
  const tx = rWidth / 2;
  const ty = h / 2;

  // (0, 0) is the upper left of the rectangle
  const pt = new DOMPoint(0, 0);
  // Create transformation matrix based on center of rectangle
  const mtr = new DOMMatrix()
    .translateSelf(-tx, -ty)
    // Important: rotateSelf takes degrees, not radians 
    .rotateSelf(a * 180 / Math.PI)
    .translateSelf(tx, ty);
  // Apply the transform
  const tPt = pt.matrixTransform(mtr);

  // Now the new call looks like this:
  drawRotateRectangle(tPt.x + x, tPt.y + y, rWidth, h, a);

这是一个演示。我画了一个额外的红点来表明您可以使用 xy.

来控制要开始绘制矩形的位置

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext("2d");

function rotateCanvas(x, y, a) {
  ctx.translate(x, y);
  ctx.rotate(a);
  ctx.translate(-x, -y);
}

function drawRotateRectangle(x, y, w, h, a) {
  rotateCanvas(x + w / 2, y + h / 2, a);
  ctx.fillRect(x, y, w, h);
  rotateCanvas(x + w / 2, y + h / 2, -a);
}

let which = false;

function render() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  const rWidth = which ? 100 : 200;

  const x = 30;
  const y = 30;
  const h = 30;
  const a = Math.PI / 3;
  const tx = rWidth / 2;
  const ty = h / 2;
  
  // Imitate the transform you use to calculate the offset point
  const pt = new DOMPoint(0, 0);
  const mtr = new DOMMatrix()
    .translateSelf(-tx, -ty)
    .rotateSelf(a * 180 / Math.PI)
    .translateSelf(tx, ty);
  const tPt = pt.matrixTransform(mtr);
  drawRotateRectangle(tPt.x + x, tPt.y + y, rWidth, h, a);

  // Just to show you can control the start point
  ctx.fillStyle = 'red';
  ctx.fillRect(x, y, 3, 3);
  ctx.fillStyle = 'black';

  which = !which;
}

render();

setInterval(render, 1000);
<canvas style="border: 1px solid black;"></canvas>