How to rotate a Canvas object around its center fo

2019-05-24 13:23发布

Hi I want to rotate this shape around its center when I move my mouse, but currently it's rotating around (0, 0). How to change my code?

Source code (also see jsfiddle):

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

class Circle {
    constructor(options) {
    this.cx = options.x;
    this.cy = options.y;
    this.radius = options.radius;
    this.color = options.color;
    this.angle = 0;
    this.toAngle = this.angle;

    this.binding();
  }

  binding() {
    const self = this;
    window.addEventListener('mousemove', (e) => {
        self.update(e.clientX, e.clientY);
    });
  }

  update(nx, ny) {
    this.toAngle = Math.atan2(ny - this.cy, nx - this.cx);
  }

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

    ctx.save();

    ctx.beginPath();

    ctx.lineWidth = 1;

    if (this.toAngle !== this.angle) {
      ctx.rotate(this.toAngle - this.angle);
    }

    ctx.strokeStyle = this.color;
    ctx.arc(this.cx, this.cy, this.radius, 0, Math.PI * 2);

    ctx.stroke();
    ctx.closePath();

    ctx.beginPath();

    ctx.fillStyle = 'black';
    ctx.fillRect(this.cx - this.radius / 4, this.cy - this.radius / 4, 20, 20);

    ctx.closePath();
    ctx.restore();
  }
}

var rotatingCircle = new Circle({
  x: 150,
  y: 100,
  radius: 40,
  color: 'black'
});

function animate() {
    rotatingCircle.render();
    requestAnimationFrame(animate);
}

animate();

3条回答
仙女界的扛把子
2楼-- · 2019-05-24 14:15

You need to:

  • first translate to the point of rotation (pivot)
  • then rotate
  • then either:
    • A: draw in at (0,0) using (-width/2, -height/2) as relative coordinate (for centered drawings)
    • B: translate back and use the object's absolute position and subtract relative coordinates for centered drawing

Modified code:

ctx.beginPath();
ctx.lineWidth = 1;

ctx.translate(this.cx, this.cy);               // translate to pivot

if (this.toAngle !== this.angle) {
  ctx.rotate(this.toAngle - this.angle);
}

ctx.strokeStyle = this.color;
ctx.arc(0, 0, this.radius, 0, Math.PI * 2);    // render at pivot

ctx.closePath();                               // must come before stroke() btw.
ctx.stroke();

ctx.beginPath();
ctx.fillStyle = 'black';
ctx.fillRect(-this.radius / 4, -this.radius / 4, 20, 20);  // render at pivot

Modified Fiddle

Bonus tip: you're currently using save()/restore() calls to maintain the transformation matrix. Another way could be to set the matrix using absolute values initially replacing the save()/restore() - so instead of the first translate():

ctx.setTranform(1,0,0,1,this.cx, this.cy);    // translate to pivot

You can also set things like styles on an individual basis for each. Regardless, it doesn't change the core solution though.

查看更多
啃猪蹄的小仙女
3楼-- · 2019-05-24 14:17

You have to first translate to the circle centre, make the rotation and then translate back

Do this before rendering the circle and the square

ctx.translate(this.cx, this.cy);
ctx.rotate(this.toAngle - this.angle);
ctx.translate(-this.cx, -this.cy);

jsfiddle below: https://jsfiddle.net/1st8Lbu8/2/

查看更多
The star\"
4楼-- · 2019-05-24 14:19

All good answers, well frustratingly no... they fail to mention that the solution only works if the current transform is at it default. They fail to mention how to get back to the default state and save and restore states.

To get the default transformation state

ctx.setTransform(1,0,0,1,0,0);

To save and restore all states

ctx.save();
ctx.transform(10,0,0,2,200,100); // set some transform state
ctx.globalAlpha = 0.4;
ctx.restore(); // each save must be followed by a restore at some point

and they can be nested

ctx.save();  // save default state
ctx.globalAlpha = 0.4;
ctx.save();  // save state with alpha = 0.4
ctx.transform(10,0,0,2,200,100); // set some transform state
ctx.restore(); // restore to alpha at 0.4
ctx.restore(); // restore to default.

setTransform completely replaces the current transformation. while transform, scale, rotate, translate, multiply the existing transform with the appropriate transform. This is handy if you have an object attached to another, and want the transformation of the first to apply to the second, and additional transforms to the second but not to the first.

ctx.rotate(Math.PI /2); // Rotates everything 90 clockwise
ctx.rotate(Math.PI /2); // Rotates everything another 90 clockwise so that
                        // everything is 180 from default

ctx.translate(1,1); // move diagonally down by 1. Origin is now at 1,1
ctx.translate(1,1); // move diagonally down by 1. Origin is now at 2,2
ctx.translate(1,1); // move diagonally down by 1. Origin is now at 3,3
ctx.translate(1,1); // move diagonally down by 1. Origin is now at 4,4

ctx.scale(2,2); // scale by 2 everything twice as big
ctx.scale(2,2); // scale by 2 everything four times as big

And an alternative that does not require the default transform state of ctx

// scaleX, scaleY are scales along axis x,y
// posX, posY is position of center point
// rotate is in radians clockwise with 0 representing the x axis across the screen
// image is an image to draw.
ctx.setTransform(scaleX,0,0,scaleY, posX, posY);
ctx.rotate(rotate);
ctx.drawImage(image,-image.width / 2, -image.height / 2);

Or if not a image but a object

ctx.setTransform(scaleX,0,0,scaleY, posX, posY);
ctx.rotate(rotate);
ctx.translate(-object.width / 2, -object.height / 2);
查看更多
登录 后发表回答