fabricjs: Manipulate selected objects - fabricjs

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

Related

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 keep scaling always at 1 on FabricJS [Edited]

I need in my application made in fabricJS, that when I modify an object with the scaling handlers, the scaling stays in 1, and that the size of the object takes the value of the height multiplied by scaleX. I can apply the same for the width. But I can not apply this in the images. How can I fix this?
I leave an example fiddle for a square, and an image, so that you can see the result.
The result in the image is similar to a crop, and I can not avoid this, because apparently, when I modify the container of the image, the _element property that also contains scaleX and scaleY does not change, so the image is not resized inside the container.
Here is jsFiddle.
var canvas = new fabric.Canvas('canvas');
var rect1 = new fabric.Rect({
width: 100,
height: 100,
left: 200,
top: 200,
angle: 0,
fill: 'rgba(0,0,255,1)',
originX: 'center',
originY: 'center'
});
canvas.add(rect1);
fabric.Image.fromURL('http://fabricjs.com/assets/pug_small.jpg', function(myImg) {
//i create an extra var for to change some image properties
var img1 = myImg.set({
left: 0,
top: 0,
width: 150,
height: 150
});
canvas.add(img1);
});
canvas.on('mouse:up', function(e) {
/*if(e.target != null){
//alert(e.target.width*e.target.scaleX);
}*/
//console.log(e.target);
canvas.getActiveObjects().forEach(function(obj) {
//if(obj.get('type')!='image'){
obj.set('width', obj.width * obj.scaleX, obj);
obj.set('height', obj.height * obj.scaleY, obj);
//if(obj.get('type')!='image'){
obj.scaleX = 1;
obj.scaleY = 1;
//}else{
// obj.scaleX = 1;
// obj.scaleY = 1;
// obj._element.scaleX = 1;
// obj._element.scaleY = 1;
// console.log("Width Contenedor Imagen: "+obj.width+" Width Imagen: "+obj._element.width);
// console.log("Scale Contenedor Imagen: "+obj.scaleX+" Scale Imagen: "+obj._element.scaleX);
//}
obj.setCoords();
//}
});
});
EDIT: New Discovery
Speaking of text, rect, images and polylines, it seems that I am modifying an external container, because the inner content remains intact. How can I modify the size of that let's say, inner container?

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>

fabric js object manage with another object

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.

Fabric.js: How to serialize clipTo objects since ToJSON does not work for it?

Any guidance with jsfiddle example showing ClipTo serialization will be appreciated? Current ToJSON function does not work when trying to serialize clipped objects. See the ToJSON implementation at the bottom of the code.
JSFiddle Link: http://jsfiddle.net/PromInc/ZxYCP/
var img01URL = 'https://www.google.com/images/srpr/logo4w.png';
var img02URL = 'http://fabricjs.com/lib/pug.jpg';
var canvas = new fabric.Canvas('c');
// Note the use of the `originX` and `originY` properties, which we set
// to 'left' and 'top', respectively. This makes the math in the `clipTo`
// functions a little bit more straight-forward.
var clipRect1 = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 180,
top: 10,
width: 200,
height: 200,
fill: '#DDD', /* use transparent for no fill */
strokeWidth: 0,
selectable: false
});
// We give these `Rect` objects a name property so the `clipTo` functions can
// find the one by which they want to be clipped.
clipRect1.set({
clipFor: 'pug'
});
canvas.add(clipRect1);
var clipRect2 = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 10,
top: 10,
width: 150,
height: 150,
fill: '#DDD', /* use transparent for no fill */
strokeWidth: 0,
selectable: false
});
// We give these `Rect` objects a name property so the `clipTo` functions can
// find the one by which they want to be clipped.
clipRect2.set({
clipFor: 'logo'
});
canvas.add(clipRect2);
function findByClipName(name) {
return _(canvas.getObjects()).where({
clipFor: name
}).first()
}
// Since the `angle` property of the Image object is stored
// in degrees, we'll use this to convert it to radians.
function degToRad(degrees) {
return degrees * (Math.PI / 180);
}
var clipByName = function (ctx) {
this.setCoords();
var clipRect = findByClipName(this.clipName);
var scaleXTo1 = (1 / this.scaleX);
var scaleYTo1 = (1 / this.scaleY);
ctx.save();
var ctxLeft = -( this.width / 2 ) + clipRect.strokeWidth;
var ctxTop = -( this.height / 2 ) + clipRect.strokeWidth;
var ctxWidth = clipRect.width - clipRect.strokeWidth;
var ctxHeight = clipRect.height - clipRect.strokeWidth;
ctx.translate( ctxLeft, ctxTop );
ctx.rotate(degToRad(this.angle * -1));
ctx.scale(scaleXTo1, scaleYTo1);
ctx.beginPath();
ctx.rect(
clipRect.left - this.oCoords.tl.x,
clipRect.top - this.oCoords.tl.y,
clipRect.width,
clipRect.height
);
ctx.closePath();
ctx.restore();
}
var pugImg = new Image();
pugImg.onload = function (img) {
var pug = new fabric.Image(pugImg, {
angle: 45,
width: 500,
height: 500,
left: 230,
top: 50,
scaleX: 0.3,
scaleY: 0.3,
clipName: 'pug',
clipTo: function(ctx) {
return _.bind(clipByName, pug)(ctx)
}
});
canvas.add(pug);
};
pugImg.src = img02URL;
var logoImg = new Image();
logoImg.onload = function (img) {
var logo = new fabric.Image(logoImg, {
angle: 0,
width: 550,
height: 190,
left: 50,
top: 50,
scaleX: 0.25,
scaleY: 0.25,
clipName: 'logo',
clipTo: function(ctx) {
return _.bind(clipByName, logo)(ctx)
}
});
canvas.add(logo);
};
logoImg.src = img01URL;
//convert to json
var serialized=JSON.stringify(canvas);
canvas.clear();
canvas.loadFromDatalessJSON(serialized);
alert(serialized);
fabricjs clipTo should be included in the json representation of the canvas by default.
So if you use toJSON you will find a clipTo field in the json representation of canvas containing the clipTo's function.
Here is a demo.

Resources