简体   繁体   English

调整形状宽度时,旋转形状在y轴上移动

[英]Rotated shape is moving on y-axis when resizing shape width

I'm trying to resize a rotated shape on canvas. 我正在尝试在画布上调整旋转的形状。 My problem is that when I call the rendering function, the shape starts "drifting" depending on the shape angle. 我的问题是,当我调用渲染功能时,形状会根据形状角度开始“漂移”。 How can I prevent this? 我怎么能阻止这个?

I've made a simplified fiddle demonstrating the problem, when the canvas is clicked, the shape is grown and for some reason it drifts upwards. 我做了一个简化的小提示来演示这个问题,当点击画布时,形状会长大,并且由于某种原因它会向上漂移。

Here's the fiddle: https://jsfiddle.net/x5gxo1p7/ 这是小提琴: https//jsfiddle.net/x5gxo1p7/

<style>
    canvas {
        position: absolute;
        box-sizing: border-box;
        border: 1px solid red;
    }
</style>
<body>
<div>
    <canvas id="canvas"></canvas>
</div>
</body>

<script type="text/javascript">
    var canvas = document.getElementById('canvas');
    canvas.width = 300;
    canvas.height= 150;
    var ctx = canvas.getContext('2d');
    var counter = 0;

    var shape = {
        top: 120,
        left: 120,
        width: 120,
        height: 60,
        rotation: Math.PI / 180 * 15
    };



    function draw() {
        var h2 = shape.height / 2;
        var w2 = shape.width / 2;

        var x = w2;
        var y = h2;


        ctx.save();
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.translate(75,37.5)
        ctx.translate(x, y);

        ctx.rotate(Math.PI / 180 * 15);
        ctx.translate(-x, -y);

        ctx.fillStyle = '#000';
        ctx.fillRect(0, 0, shape.width, shape.height);
        ctx.restore();
}


canvas.addEventListener('click', function() {

    shape.width = shape.width + 15;
    window.requestAnimationFrame(draw.bind(this));
});

window.requestAnimationFrame(draw.bind(this));
</script>

In the "real" code the shape is resized when the resize-handle is clicked and moved but I think this example demonstrates the problem sufficiently. 在“真实”代码中,单击并移动调整大小句柄时调整形状的大小,但我认为此示例充分展示了问题。

EDIT: updated fiddle to clarify the issue: 编辑:更新小提琴澄清问题:

https://jsfiddle.net/x5gxo1p7/9/ https://jsfiddle.net/x5gxo1p7/9/

Always use local coordinates to define shapes. 始终使用局部坐标来定义形状。

When rendering content that is intended to be transformed the content should be in its own (local) coordinate system. 在渲染要转换的内容时,内容应位于其自己的(本地)坐标系中。 Think of a image. 想象一下图像。 the top left pixel is always at 0,0 on the image no matter where you render it. 无论您在何处渲染,左上角的像素始终为0。 The pixels are at their local coordinates, when rendered they are moved to the (world) canvas coordinates via the current transformation. 像素位于其局部坐标处,在渲染时,它们通过当前变换移动到(世界)画布坐标。

So if you make your shape with coordinates set to its local, making the rotation point at its local origin (0,0) the display coordinates are stored separately as world coordinates 因此,如果您将坐标设置为其局部的形状,使旋转点位于其本地原点(0,0),则显示坐标将作为世界坐标单独存储

var shape = {
    top: -30,   // local coordinates with rotation origin 
    left: -60,  // at 0,0
    width: 120,
    height: 60,
    world : {
         x : canvas.width / 2,
         y : canvas.height / 2,
         rot : Math.PI / 12,   // 15deg clockwise
    }
};

Now you don't have to mess about with translating forward and back... blah blah total pain. 现在你不必乱用前后翻译......等等等等。

Just 只是

ctx.save();
ctx.translate(shape.world.x,shape.world.y);
ctx.rotate(shape.world.rot);
ctx.fillRect(shape.left, shape.top, shape.width, shape.height)
ctx.restore();

or event quicker and eliminating the need to use save and restore 或事件更快,无需使用保存和恢复

ctx.setTransform(1,0,0,1,shape.world.x,shape.world.y);
ctx.rotate(shape.world.rot);
ctx.fillRect(shape.left, shape.top, shape.width, shape.height);

The local shape origin (0,0) is where the transformation places the translation. 局部形状原点(0,0)是转换放置转换的位置。

This greatly simplifies a lot of the work that has to be done 这大大简化了许多必须完成的工作

 var canvas = document.getElementById('canvas'); canvas.width = 300; canvas.height= 150; var ctx = canvas.getContext('2d'); ctx.fillStyle = "black"; ctx.strokeStyle = "red"; ctx.lineWidth = 2; var shape = { top: -30, // local coordinates with rotation origin left: -60, // at 0,0 width: 120, height: 60, world : { x : canvas.width / 2, y : canvas.height / 2, rot : Math.PI / 12, // 15deg clockwise } }; function draw() { ctx.setTransform(1,0,0,1,0,0); // to clear use default transform ctx.clearRect(0, 0, canvas.width, canvas.height); // you were scaling the shape, that can be done via a transform // once you have moved the shape to the world coordinates. ctx.setTransform(1,0,0,1,shape.world.x,shape.world.y); ctx.rotate(shape.world.rot); // after the transformations have moved the local to the world // you can ignore the canvas coordinates and work within the objects // local. In this case showing the unscaled box ctx.strokeRect(shape.left, shape.top, shape.width, shape.height); // and a line above the box ctx.strokeRect(shape.left, shape.top - 5, shape.width, 1); ctx.scale(0.5,0.5); // the scaling you were doing ctx.fillRect(shape.left, shape.top, shape.width, shape.height); } canvas.addEventListener('click', function() { shape.width += 15; shape.left -= 15 / 2; shape.world.rot += Math.PI / 45; // rotate to illustrate location // of local origin var distToMove = 15/2; shape.world.x += Math.cos(shape.world.rot) * distToMove; shape.world.y += Math.sin(shape.world.rot) * distToMove; draw(); }); // no need to use requestAnimationFrame (RAF) if you are not animation // but its not wrong. Nor do you need to bind this (in this case // this = window) to the callback RAF does not bind a context // to the callback /*window.requestAnimationFrame(draw.bind(this));*/ requestAnimationFrame(draw); // functionaly identical // or just /*draw()*/ //will work 
  body { font-family : Arial,"Helvetica Neue",Helvetica,sans-serif; font-size : 12px; color : #242729;} /* SO font currently being used */ canvas { border: 1px solid red; } 
 <canvas id="canvas"></canvas> <p>Click to grow "and rotate" (I add that to illustrate the local origin)</p> <p>I have added a red box and a line above the box, showing how using the local coordinates to define a shape makes it a lot easier to then manipulate that shape when rendering "see code comments".</p> 

Try this. 试试这个。 You had ctx.translate() used where it was not entirely necessary. 你有ctx.translate()用于不完全必要的地方。 That caused the problems. 这导致了问题。

<script type="text/javascript">
var canvas = document.getElementById('canvas');
canvas.width = 300;
canvas.height= 150;
var ctx = canvas.getContext('2d');
var counter = 0;

var shape = {
    top: 120,
    left: 120,
    width: 120,
    height: 60,
    rotation: Math.PI / 180 * 15
};

function draw() {

    ctx.save();
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.translate(75,37.5)

    ctx.rotate(Math.PI / 180 * 15);

    ctx.fillStyle = '#000';
    ctx.fillRect(0, 0, shape.width, shape.height);
    ctx.restore();
}

canvas.addEventListener('click', function() {

    shape.width = shape.width + 15;
    window.requestAnimationFrame(draw.bind(this));
});

window.requestAnimationFrame(draw.bind(this));
</script>

This is happening because the x and y are set as the half value of the shape size, which completely changes its position. 发生这种情况是因为xy被设置为形状大小的一半值,这完全改变了它的位置。

You should set a point for the center of the shape, anyway. 无论如何,你应该为形状的中心设置一个点。 I set this point as ctx.canvas.[width or height] / 2 , the half of the canvas. 我将此点设置为ctx.canvas.[width or height] / 2 ,画布的一半。

var h2 = shape.height / 2;
var w2 = shape.width / 2;

var x = (ctx.canvas.width / 2) - w2;
var y = (ctx.canvas.height / 2) - h2;

ctx.save();

ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(x + (shape.width / 2), y + (shape.height / 2));

ctx.rotate(((shape.rotation * Math.PI) / 180) * 15);

ctx.fillStyle = '#000';
ctx.fillRect(-shape.width / 2, -shape.height / 2, shape.width, shape.height);
ctx.restore();

Fiddle . 小提琴

Found a solution, problem was that I wasn't calculating the new center point coordinates. 找到了一个解决方案,问题是我没有计算新的中心点坐标。

The new fiddle with solution: https://jsfiddle.net/HTxGb/151/ 解决方案的新小提琴: https//jsfiddle.net/HTxGb/151/

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

canvas.width =500;
canvas.height = 500;

var x = canvas.width/2;
var y = canvas.height/2;
var rectw = 20;
var recth = 20;
var rectx = -rectw/2;
var recty = -recth/2;
var rotation = 0;
var addedRotation = Math.PI/12;
var addedWidth = 20;
var addedHeight = 10;

var draw = function() {
  ctx.save();
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.translate(x, y);
  ctx.rotate(rotation);
  ctx.fillRect(rectx, recty, rectw, recth);
  ctx.restore();
}


document.getElementById('growXRight').addEventListener('click', function() {
  rectx -= addedWidth/2;
  x += addedWidth/2 * Math.cos(rotation);
  y -= addedWidth/2 * Math.sin(-rotation);
  rectw += addedWidth;
  draw();
})

document.getElementById('growXLeft').addEventListener('click', function() {
  rectx -= addedWidth/2;
  x -= addedWidth/2 * Math.cos(rotation);
  y += addedWidth/2 * Math.sin(-rotation);
  rectw += addedWidth;
  draw();
})

document.getElementById('growYTop').addEventListener('click', function() {
  recty -= addedHeight/2;
  x += addedHeight/2 * Math.sin(rotation);
  y -= addedHeight/2 * Math.cos(-rotation);
  recth += addedHeight;
  draw();
})

document.getElementById('growYBottom').addEventListener('click', function() {
  recty -= addedHeight/2;
  x -= addedHeight/2 * Math.sin(rotation);
  y += addedHeight/2 * Math.cos(-rotation);
  recth += addedHeight;
  draw();
})

document.getElementById('rotatePlus').addEventListener('click', function() {
  rotation += addedRotation;
  rotation = rotation % (Math.PI*2);
  if(rotation % Math.PI*2 < 0) {
    rotation += Math.PI*2;
  }
  draw();
})

document.getElementById('rotateMinus').addEventListener('click', function() {
  rotation -= addedRotation;
  rotation = rotation % (Math.PI*2);
  if(rotation % Math.PI*2 < 0) {
    rotation += Math.PI*2;
  }
  draw();
})

draw();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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