The rotating shape moves along the y axis as the shape resizes

I am trying to resize the rotation on the canvas. My problem is that when I call the render function, the form starts to β€œdrift” depending on the angle of the form. How can I prevent this?

I made a simplified fiddle demonstrating the problem, when I clicked on the canvas, the shape grew and for some reason drifts up.

Here's the fiddle: 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 form changes when the control is resized and resized, but I think this example demonstrates the problem enough.

EDIT: Updated script to clarify the problem:

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

+6
source share
4 answers

The solution found, the problem was that I did not calculate the new coordinates of the center point.

New fiddle with solution: 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(); 
0
source

Always use local coordinates to define shapes.

When rendering content intended for conversion, the content should be in its own (local) coordinate system. Think about the image. the top left pixel is always at 0.0 in the image no matter where you render it. Pixels are in their local coordinates, during visualization they move to the canvas (world) coordinates through the current transformation.

So, if you make your shape with the coordinates set to its local point, making the pivot point in its local source (0,0), the display coordinates are saved separately as world coordinates

 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 bother translating back and forth ... blah blah is a complete 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 faster 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 origin (0,0) is where the transformation places the translation.

This greatly simplifies the work that needs 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> 
+1
source

Try it. You had ctx.translate () where it was not completely necessary. This caused a problem.

 <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> 
0
source

This is because the x and y values ​​are given as the value of half the size of the form, which completely changes its position.

In any case, you must set a point for the center of the figure. I set this point as ctx.canvas.[width or height] / 2 , half the canvas.

 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

0
source

All Articles