fabric js object manage with another object - fabricjs

My canvas has two objects. The first one is selectable true and the second one is selectable false. I am looking to manage the second object through the first object's action.
If the first object moves, then the second objects need to move in the same direction. If the first one rotates, then the second one rotates at the same angle in the second one's position.
I have tried adding events (moving,rotating) to the first selectable object, and then getting the required values from the first object and setting it to second object, but rotating is not working properly. My code is as follows:
imageOne.on('rotating', function (evt) { imageThree.angle = imageOne.getAngle(); });

var canvas = new fabric.Canvas('c');
var evented = false;
var rect1 = new fabric.Rect({
left: 50,
top: 60,
fill: 'blue',
width: 150,
height: 150,
});
var rect2 = new fabric.Rect({
left: 210,
top: 60,
fill: 'magenta',
width: 150,
height: 150,
selectable: false
});
canvas.add(rect1,rect2);
function rect1MouseDown(option){
this.mousesDownLeft = this.left;
this.mousesDownTop = this.top;
this.rect2Left = rect2.left;
this.rect2Top = rect2.top;
}
function rect1Move(option){
rect2.left = this.rect2Left+ this.left - this.mousesDownLeft ;
rect2.top = this.rect2Top+ this.top- this.mousesDownTop;
rect2.setCoords();
}
function rect1Rotating(options){
rect2.setAngle(this.angle);
}
register();
function register(){
if(evented) return;
rect1.on('moving', rect1Move);
rect1.on('mousedown', rect1MouseDown);
rect1.on('rotating', rect1Rotating);
evented = true;
}
function unRegister(){
rect1.off('moving');
rect1.off('mousedown');
rect2.on('rotating');
evented = false;
}
canvas {
border: blue dotted 2px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.19/fabric.min.js"></script>
<button onclick='register()'>Register Event</button>
<button onclick='unRegister()'>Unregister Event</button><br>
<canvas id="c" width="400" height="400"></canvas>
You can use obj.setAngle() for version <1.7.19 and obj.rotate() for version 2 , to set angle to an object.

Related

If a FabricJS object is moved by code, the mouse selection does not appear to work

If I move an object by code, when the mouse is used to select the object, the selection seems to select the object based on its old position. In the example below, the object can be selected by its old position, and not in its current position.
Am I doing something wrong, or is this a bug in FabricJS?
var canvas = window._canvas = new fabric.Canvas('c');
var rect = new fabric.Rect({
left: 50,
top: 50,
width: 50,
height: 50,
fill: 'rgba(255,0,0,0.5)',
});
canvas.add(rect);
var rect_json = rect.toJSON();
rect_json.left += 100;
rect.set(rect_json);
See this fiddle to see it in action.
You need to call setCoords in order to make the control positions recalculate.
Please check this link When to call setCoords.
var canvas = window._canvas = new fabric.Canvas('c');
var rect = new fabric.Rect({
left: 50,
top: 50,
width: 50,
height: 50,
fill: 'rgba(255,0,0,0.5)',
});
canvas.add(rect);
rect.set({ left: 300 }).setCoords()

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();
});
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

How to align object by bounding box in FabricJS?

Wondering if there is a way to align objects in FabricJs by their bounding box?
I'm using obj.getBoundingRect() function to determine objects bounders, then compare them with a bounding box (BB) coordinates of an Active one (that one which I move). If I see that something falls between some gap (let's say 10px) I assign an active object top to be the same top as a comparable element by using a .setTop() property.
The problem is that TOP is not a right attribute to use, since the top of the bounding box may differ between elements. For example, 2 elements with the same top but different angle will have different Bounding Box Top...
Hope you see my point...
https://jsfiddle.net/redlive/hwcu1p4f/
var canvas = this.__canvas = new fabric.Canvas('canvas');
//fabric.Object.prototype.transparentCorners = false;
var red = new fabric.Rect({
id: 1,
left: 100,
top: 50,
width: 100,
height: 100,
fill: 'red',
angle: 0,
padding: 10
});
canvas.add(red);
var green = new fabric.Rect({
id: 2,
left: 250,
top: 180,
width: 100,
height: 100,
fill: 'green',
angle: 45,
padding: 10
});
canvas.add(green);
canvas.renderAll();
canvas.on("object:moving", function(e){
const draggableObj = e.target;
const draggableObjBound = draggableObj.getBoundingRect();
canvas.forEachObject(function(obj) {
if (obj.id !== draggableObj.id) {
var bound = obj.getBoundingRect();
if (draggableObjBound.top > bound.top - 10 && draggableObjBound.top < bound.top + 10) {
draggableObj.setTop(obj.getTop());
}
}
});
});
canvas.forEachObject(function(obj) {
var setCoords = obj.setCoords.bind(obj);
obj.on({
moving: setCoords,
scaling: setCoords,
rotating: setCoords
});
});
canvas.on('after:render', function() {
canvas.contextContainer.strokeStyle = '#555';
canvas.forEachObject(function(obj) {
var bound = obj.getBoundingRect();
canvas.contextContainer.strokeRect(
bound.left,
bound.top,
bound.width,
bound.height
);
})
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.0.0-rc.3/fabric.js"></script>
<canvas id="canvas" width="800" height="500" style="border:1px solid #ccc"></canvas>
You should use the center to align them, that is not gonna change.
to align the bounding box at left 5 for example:
1) calculate bounding box.
2) set the position of the object to 5 + bb.width/2 considering center.
In this case the bounding rects get aligned.
var canvas = this.__canvas = new fabric.Canvas('canvas');
//fabric.Object.prototype.transparentCorners = false;
var red = new fabric.Rect({
id: 1,
left: 100,
top: 50,
width: 100,
height: 100,
fill: 'red',
angle: 0,
padding: 10
});
canvas.add(red);
var green = new fabric.Rect({
id: 2,
left: 250,
top: 180,
width: 100,
height: 100,
fill: 'green',
angle: 45,
padding: 10
});
canvas.add(green);
//ALIGN EVERYTHING TO 5
canvas.forEachObject(function(object) {
var bb = object.getBoundingRect();
object.setPositionByOrigin({ x: 5 + bb.width/2, y: bb.top }, 'center', 'center');
object.setCoords();
});
canvas.renderAll();
canvas.on("object:moving", function(e){
const draggableObj = e.target;
const draggableObjBound = draggableObj.getBoundingRect(true, true);
canvas.forEachObject(function(obj) {
if (obj.id !== draggableObj.id) {
var bound = obj.getBoundingRect(true, true);
if (draggableObjBound.top > bound.top - 10 && draggableObjBound.top < bound.top + 10) {
draggableObj.setPositionByOrigin({ x: draggableObj.left, y: bound.top + draggableObjBound.height/2 }, draggableObj.originX, 'center');
}
}
});
});
canvas.forEachObject(function(obj) {
var setCoords = obj.setCoords.bind(obj);
obj.on({
moving: setCoords,
scaling: setCoords,
rotating: setCoords
});
});
canvas.on('after:render', function() {
canvas.contextContainer.strokeStyle = '#555';
canvas.forEachObject(function(obj) {
var bound = obj.getBoundingRect(true, true);
canvas.contextContainer.strokeRect(
bound.left,
bound.top,
bound.width,
bound.height
);
})
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.0.0-rc.3/fabric.js"></script>
<canvas id="canvas" width="800" height="500" style="border:1px solid #ccc"></canvas>

fabricjs: Manipulate selected objects

i try to change the color of all selected objects on my canvas. I figured out that i can change properties of the group itself (this.canvas.getActiveGroup().setFill(...)).
Since i want to change different properties depending on the type of the selected object, i can't use this function. Instead i try to iterate over all selected objects and manipulate them.
this.canvas.getActiveGroup().getObjects().forEach(obj =>
{
if(obj instanceof fabric.Path)
{
obj.setStroke(color);
}
else
{
obj.setFill(color);
}
});
this.canvas.renderAll();
The color will be changed, but unfortunately after deselecting the objects all styles will be changed back.
I think the styles will be applied to the group, because when selecting again all elements will appear in the right color...
When i remove the activeGroup (this.canvas.discardActiveGroup()) before, it will work but will bring some other problems.
Btw.: the inspector (dev tools) displays the right color. After resizing a specific element it will also update its color.
Has anyone similar problems and knows a solution?
Best regards
Daniel
DEMO
var color = 'black';
function changeColor() {
var r = getRandomArbitrary(0, 225);
var g = getRandomArbitrary(0, 225);
var b = getRandomArbitrary(0, 225);
color = 'rgb(' + r + ',' + g + ',' + b + ')';
fillColorOb();
}
function getRandomArbitrary(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
var c = new fabric.Canvas('canvas');
c.add(new fabric.Circle({
left: 50,
top: 50,
radius: 50,
originX: 'center',
originY: 'center',
}))
c.add(new fabric.Circle({
left: 50,
top: 150,
radius: 50,
originX: 'center',
originY: 'center',
}))
c.add(new fabric.Circle({
left: 150,
top: 100,
radius: 50,
originX: 'center',
originY: 'center',
}))
c.renderAll();
function fillColorOb() {
var objs = c.getActiveGroup();
if(!objs) return;
objs.forEachObject(function(obj) {
if (obj instanceof fabric.Path) {
obj.setStroke(color);
} else {
obj.setFill(color);
}
c.renderAll();
});
}
fabric.Group.prototype._restoreObjectState = function(object) {
this.realizeTransform(object);
object.setCoords();
object.hasControls = object.__origHasControls;
delete object.__origHasControls;
object.set('active', false);
delete object.group;
object.dirty = true; // it will render on next renderAll() call
return this;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.16/fabric.min.js"></script>
<button type="button" name="button" onclick="changeColor();">color</button><br>
<canvas id="canvas" width="400" height="400"></canvas>
If I am , then this gonna work, check demo, add this function
fabric.Group.prototype._restoreObjectState = function(object) {
this.realizeTransform(object);
object.setCoords();
object.hasControls = object.__origHasControls;
delete object.__origHasControls;
object.set('active', false);
delete object.group;
object.dirty = true; // it will render on next renderAll() call
return this;
}

Fabric.js rectangle is not showing up

Why the rectangle is not showing up? I've found the examples at GitHub, official site etc. But they're not working for me.
<script type="text/javascript">
var fCanvas;
window.onload = function (){
var oldCanvas = document.getElementById('canvas');
fCanvas = new fabric.Canvas('canvas',
{
width: oldCanvas.parentNode.clientWidth,
height: oldCanvas.parentNode.clientHeight
});
}
function CreateGraphicObject(id){
//...
var rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'red',
width: 20,
height: 20
});
fCanvas.add(rect);
//fCanvas.renderAll(); - did not help
}
</script>
Thanks.
I had created a fiddle. I hope it will help you.
https://jsfiddle.net/mullainathan/hgnvaqej/
You have created function but never called it, so the rectangle never gets created.
You just need to call the function, in your code I suppose the best place to call it after creating the canvas:
window.onload = function (){
var oldCanvas = document.getElementById('canvas');
fCanvas = new fabric.Canvas('canvas',
{
width: oldCanvas.parentNode.clientWidth,
height: oldCanvas.parentNode.clientHeight
});
CreateGraphicObject(); //I didn't pass any "id" because it makes no sense in the given code
}
function CreateGraphicObject(){
var rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'red',
width: 20,
height: 20
});
fCanvas.add(rect);
fCanvas.renderAll();
}

Resources