Search code examples
fabricjs

fabric.js rotate object around canvas center smoothly


I try to rotate an object around the canvas center smoothly using a slider, I use fabric.util.rotatePoint to find the new center point and set the new angle,but it seems not exactly around the center point and the object position is jumping.

https://jsfiddle.net/j0g1tLsb/26/

here is my code doing this:

const canvas = new fabric.Canvas('c', { width: innerWidth, height: innerHeight });
canvas.controlsAboveOverlay = true;
canvas.preserveObjectStacking = true;

const rect = new fabric.Rect({
    width: 300,
  height: 150,
  left: 400,
  top: 400,
  fill: "lightgray",
    originX: 'center',
  originY: 'center'
});

const centerPoint = new fabric.Circle({ 
   originX: 'center',
   originY: 'center',
   top: innerHeight/2, 
   left: innerWidth/2,
   radius: 10, 
   fill: 'red',
   hasControls: false,
   selectable:false
});


const log = new fabric.Text('', {
    left: 30,
  top: 30,
  originX: 'left',
  originY: 'top',
  fontSize: 18,
  evented: false,
  selectable: false
});

canvas.add(rect, centerPoint, log);

document.getElementById('img-rotation').oninput = function() {
      rect.set('angle', this.value);

      var posNewCenter = fabric.util.rotatePoint(
                            rect.getCenterPoint(),
                            canvas.getVpCenter(),
                            fabric.util.degreesToRadians(this.value)
                        );

                        rect.set({
                            left: posNewCenter.x,
                            top: posNewCenter.y,
                            angle: this.value
                        });

      log.set('text', `angle: ${Math.round(rect.angle)} \nleft: ${rect.left} \ntop: ${rect.top}`);
      canvas.requestRenderAll();
    };


rect.on('modified', () => {
    log.set('text', `angle: ${Math.round(rect.angle)} \nleft: ${rect.left} \ntop: ${rect.top}`);
  canvas.renderAll();
});

Solution

  • Your problem is in getting center point from the rectangle every time. You need just set new Point from the original rectangle position.

    var posNewCenter = fabric.util.rotatePoint(
        new fabric.Point(400, 400), //here is your mistake
        canvas.getVpCenter(),
        fabric.util.degreesToRadians(this.value)
    );
    rect.set({
        left: posNewCenter.x,
        top: posNewCenter.y,
        angle: this.value
    });
    

    Working fiddle

    UPDATE:

    In order of the modification of the Rectangle you need to use object:modified event to reassign top and left coordinates.

    First of all declare top and left variables and use them inside rectangle object:

    let top = 400;
    let left = 400;
    const rect = new fabric.Rect({
      ...
      left: left,
      top: top,
      ...
      targetObj: 'rectangle' //you can use any value or ID, it's only for targeting purpose
    });
    

    Then you need to check when object is modified. If event is fired then check if it is rectangle.

    canvas.on('object:modified', (e) => {
        if (e.target.hasOwnProperty('targetObj') && e.target.targetObj === 'rectangle') {
        left = e.target.get('left');
        top = e.target.get('top');
      }
    })
    

    Finally, use left and top variables inside oninput event of the slider:

    var posNewCenter = fabric.util.rotatePoint(
        new fabric.Point(left, top),
        canvas.getVpCenter(),
        fabric.util.degreesToRadians(this.value)
    );
    

    Working updated fiddle