Eugene Vilder
Eugene Vilder

Reputation: 562

FabricJS - Continues rotation on object rotation event

I'm experiencing weird behavior of an element when using object:rotating event function. The element continuously rotates with some extra angle. Looks like the angle accumulates all the time to the previous angle...

Basically, what I try to achieve is a proper position and angle of the YELLOW element when rotating a red one.

Here is jsFiddle.Please try to rotate the red element.

var canvas = this.__canvas = new fabric.Canvas('paper',{
                preserveObjectStacking: true
            });

const data = {
    red:{
    	top: 50,
        left: 50,
        angle: -30,
        width: 300,
        height: 200
    },
	yellow:{
    	top: 50,
        left: 50,
        angle: 20,
        width: 100,
        height: 100
    }
};
const yellow = new fabric.Rect({
    left: data.yellow.left, 
    top:  data.yellow.top,
    fill: 'yellow',
    angle:  data.yellow.angle,
    width: data.yellow.width,
    height: data.yellow.height,
    originX: 'center',
    originY: 'center',
    opacity: .7
});

yellow.setPositionByOrigin({x:yellow.getLeft() + yellow.getWidth() / 2, y: yellow.getTop() + yellow.getHeight() / 2}, 'center', 'center');

fabric.Image.fromURL('//images.sftcdn.net/images/t_optimized,f_auto/p/85e6f558-9a68-11e6-bdf1-00163ed833e7/1312338326/slack-logo.png', function(img) {
    const patternSourceCanvas = new fabric.StaticCanvas();
    patternSourceCanvas.enableRetinaScaling = false;
    patternSourceCanvas.setBackgroundColor('red', patternSourceCanvas.renderAll.bind(patternSourceCanvas));
    patternSourceCanvas.setBackgroundImage(img, patternSourceCanvas.renderAll.bind(patternSourceCanvas), {
        top: yellow.getTop() - yellow.getWidth() / 2,
        left: yellow.getLeft() - yellow.getHeight() / 2,
        width: yellow.getWidth(),
        height: yellow.getHeight(),
        angle: yellow.getAngle(),
        scaleX: 1,
        scaleY: 1,
        originX: 'center',
        originY: 'center',
    });

    var pattern = new fabric.Pattern({
        source: function() {
            patternSourceCanvas.setDimensions({
                width: data.red.width,
                height: data.red.height
            });
            patternSourceCanvas.renderAll();
            return patternSourceCanvas.getElement();
        },
        repeat: 'no-repeat'
    });

    const red = new fabric.Rect({
        left: data.red.left,
        top: data.red.top,
        angle: data.red.angle,
        fill: pattern,
        width: data.red.width,
        height: data.red.height,
        originX: 'center',
        originY: 'center',
        patternSourceCanvas: patternSourceCanvas
    });
	red.setPositionByOrigin({x:red.getLeft() + red.getWidth() / 2, y: red.getTop() + red.getHeight() / 2}, 'center', 'center');
    
    canvas.add(red);
    canvas.add(yellow);
   
    canvas.on('object:rendered', function(){
		console.log('Rednder')
    });
    
    red.on('rotating', function(){
	    updateYellowPosition();
    });
    red.on('moving', function(){
	    updateYellowPosition();
    });
   
    updateYellowPosition();
   
   
    function updateYellowPosition(){
    	// Here I would like to have some logic which will identify proper position
        // (left, top and angle) of Red's background image location and apply those 
        // to the Yellow element
        console.log('Yellow left top', red, red.angle, red.getAngle(), yellow.getLeft(), yellow.getTop());
        const shiftX = red.patternSourceCanvas.backgroundImage.left;
        const shiftY = red.patternSourceCanvas.backgroundImage.top;
        const backgroundAngle = red.patternSourceCanvas.backgroundImage.angle;
        const redCenterPoint = red.getCenterPoint();
        const newYellowPos = fabric.util.rotatePoint(
            new fabric.Point( 
            	yellow.getLeft(), 
				yellow.getTop()
            ), // NOT SURE
            new fabric.Point(
            	redCenterPoint.x, 
                redCenterPoint.y
            ), // NOT SURE
            fabric.util.degreesToRadians(red.getAngle())  // NOT SURE
        );  
        
        yellow.setPositionByOrigin (
        	{
            	x: newYellowPos.x,
	            y: newYellowPos.y
	        },
            'center',
            'center'
        );

        yellow.rotate(yellow.getAngle() + red.getAngle()).setCoords();
       	canvas.renderAll();
    }

},{crossOrigin: 'Anonymous'});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.19/fabric.min.js"></script>
<canvas id="paper" width="400" height="400" style="border:1px solid #ccc"></canvas>

Upvotes: 0

Views: 2342

Answers (1)

AndreaBogazzi
AndreaBogazzi

Reputation: 14731

This example shows you how to handle transform in a generic way in fabricJS.

You should find the relative position from the center of the red rectangle. save it in a variable.

Each transform step, calculate the red rect transform matrix, and use that matrix to figure out where in the canvas is now that relative position.

position the yellow rectangle there and give it the right angle.

I updated the example to work with scaleX and scaleY too.

It should work aslo with skewing if you would ever need it.

Matrix can transform all that the 2d canvas can do, so you should learn how to use them, they often offer the solution and a code shortcut

var canvas = this.__canvas = new fabric.Canvas('paper',{
                preserveObjectStacking: true
            });

const data = {
    red:{
    	top: 50,
        left: 50,
        angle: -30,
        width: 300,
        height: 200
    },
	yellow:{
    	top: 50,
        left: 50,
        angle: 20,
        width: 100,
        height: 100
    }
};
const yellow = new fabric.Rect({
    left: data.yellow.left, 
    top:  data.yellow.top,
    fill: 'yellow',
    angle:  data.yellow.angle,
    width: data.yellow.width,
    height: data.yellow.height,
    originX: 'center',
    originY: 'center',
    opacity: .7
});

yellow.setPositionByOrigin({x:yellow.getLeft() + yellow.getWidth() / 2, y: yellow.getTop() + yellow.getHeight() / 2}, 'center', 'center');

fabric.Image.fromURL('//images.sftcdn.net/images/t_optimized,f_auto/p/85e6f558-9a68-11e6-bdf1-00163ed833e7/1312338326/slack-logo.png', function(img) {
    const patternSourceCanvas = new fabric.StaticCanvas();
    patternSourceCanvas.enableRetinaScaling = false;
    patternSourceCanvas.setBackgroundColor('red', patternSourceCanvas.renderAll.bind(patternSourceCanvas));
    patternSourceCanvas.setBackgroundImage(img, patternSourceCanvas.renderAll.bind(patternSourceCanvas), {
        top: yellow.getTop() - yellow.getWidth() / 2,
        left: yellow.getLeft() - yellow.getHeight() / 2,
        width: yellow.getWidth(),
        height: yellow.getHeight(),
        angle: yellow.getAngle(),
        scaleX: 1,
        scaleY: 1,
        originX: 'center',
        originY: 'center',
    });

    var pattern = new fabric.Pattern({
        source: function() {
            patternSourceCanvas.setDimensions({
                width: data.red.width,
                height: data.red.height
            });
            patternSourceCanvas.renderAll();
            return patternSourceCanvas.getElement();
        },
        repeat: 'no-repeat'
    });

    const red = new fabric.Rect({
        left: data.red.left,
        top: data.red.top,
        angle: data.red.angle,
        fill: pattern,
        width: data.red.width,
        height: data.red.height,
        originX: 'center',
        originY: 'center',
        patternSourceCanvas: patternSourceCanvas
    });
	red.setPositionByOrigin({x:red.getLeft() + red.getWidth() / 2, y: red.getTop() + red.getHeight() / 2}, 'center', 'center');
  
  // you are working with center coordinates
// find the distance between the 2 centers and the angle difference.
var angleDiff = yellow.angle; // the image to cover is angled as yellow angle, relative to red angle
var centerDiff = yellow.getCenterPoint().subtract(red.getCenterPoint());
    
    canvas.add(red);
    canvas.add(yellow);
   
    canvas.on('object:rendered', function(){
		console.log('Rednder')
    });
    
    red.on('rotating', function(){
	    updateYellowPosition();
    });
    red.on('moving', function(){
	    updateYellowPosition();
    });
    red.on('scaling', function(){
	    updateYellowPosition();
    });
   
    updateYellowPosition();
   
   
    function updateYellowPosition(){
    	// Here I would like to have some logic which will identify proper position
        // (left, top and angle) of Red's background image location and apply those 
        // to the Yellow element
        var matrix = red.calcTransformMatrix(); // get all the position information of red
        var newCenterForYellow = fabric.util.transformPoint(centerDiff, matrix);
        yellow.top = newCenterForYellow.y;
        yellow.left = newCenterForYellow.x;
        yellow.angle = red.angle + angleDiff;
         yellow.scaleX = red.scaleX;
         yellow.scaleY = red.scaleY;
       	canvas.renderAll();
    }

},{crossOrigin: 'Anonymous'});
canvas {

}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.19/fabric.min.js"></script>
<canvas id="paper" width="400" height="400" style="border:1px solid #ccc"></canvas>

Upvotes: 3

Related Questions