Fabricjs: cloning a subclassed object makes the clone invisible - javascript

I want to make a copy of a fabric object that is a sub-class of a regular fabric class (i.e. a new object with the same properties as the original). Cloning seemed to be the way to go, and indeed cloning a regular object, such as a Rect, works fine. But if I use the same code to clone an instance of a sub-class, the resulting object is not visible on the canvas (although it is listed in the results of canvas.getObjects()).
What am I doing wrong?
Here is a minimal example. This is also at https://codepen.io/micrology/pen/dyKPbRO Running this displays 3 rectangles, two red but only one yellow. There should be 2 yellow rectangles.
var canvas = new fabric.Canvas('canvas')
var rect = new fabric.Rect({
top: 100,
left: 100,
width: 60,
height: 70,
fill: 'red',
})
canvas.add(rect)
var clonedRect
rect.clone((obj) => {
clonedRect = obj
clonedRect.set({left: obj.left + 10, top: obj.top + 10})
})
canvas.add(clonedRect)
var CustomRect = fabric.util.createClass(fabric.Rect, {
type: 'rect',
initialize: function (options) {
this.callSuper('initialize', options)
},
})
var customRect = new CustomRect({
top: 200,
left: 200,
width: 50,
height: 50,
fill: '#ffff00',
})
canvas.add(customRect)
var clonedCustomRect
customRect.clone((obj) => {
clonedCustomRect = obj
clonedCustomRect.set({left: obj.left + 10, top: obj.top + 10})
})
canvas.add(clonedCustomRect)

For people still having this problem, there is currently an open discussion.

Related

fabricjs objects order in group

I have no idea how to order objects in group other then repeat add and remove to sort the objects.
I tried unshift on group objects but it does not save state.
$(document).on("click", "#addToGroup", function() {
var objects = canvas.getActiveObject();
if(objects && objects.type === "activeSelection") {
objects.toGroup();
canvas.requestRenderAll();
}
});
count = 1;
$(document).on("click", "#addObject", function() {
canvas.add(new fabric.Rect({
width: 100, height: 100, left: 100 + (count * 20), top: 20 + (count * 10), angle: -10,
fill: 'rgba(0,200,0,1)'
}));
});
Template JSFiddle
You can use fabric.Object.prototype.moveTo(). There is an undocumented behavior: if the object you call it upon is a part of a group, the object is moved within the group's _objects array, effectively changing its z-index when the group is redrawn.
Also, make sure to somehow force fabric to redraw the group after that, e.g. set group.dirty = true.
const canvas = new fabric.Canvas("c")
const a = new fabric.Rect({
width: 100,
height: 100,
left: 100,
top: 20,
angle: -10,
fill: 'rgba(0,200,0,1)'
})
const b = new fabric.Rect({
width: 50,
height: 100,
left: 150,
top: 50,
angle: 45,
stroke: '#eee',
strokeWidth: 10,
fill: 'rgba(0,0,200,1)'
})
const c = new fabric.Circle({
radius: 50,
left: 150,
top: 75,
fill: '#aac'
})
const group = new fabric.Group([a, b, c])
canvas.add(group).requestRenderAll()
document.querySelector('#b1').addEventListener('click', () => {
const bottomChild = group.getObjects()[0]
bottomChild.moveTo(group.size() - 1)
// force fabric to redraw the group
group.dirty = true
canvas.requestRenderAll()
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.6/fabric.js"></script>
<canvas id='c' width="400" height="200"></canvas>
<button id='b1'>move bottom to top</button>

Fabric.js: Assigning background color to group

I am trying to set a background color for a group without affecting its contained objects.
So far my code looks like this:
var canvas = new fabric.Canvas('canvas');
var helloText = new fabric.Text('hello', {
fontSize: 30,
top: 10, left: 10,
originX: 0, originY: 0
});
var worldText = new fabric.Text('world!', {
fontSize: 40,
top: 50, left: 100,
originX: 0, originY: 0
});
var group = new fabric.Group([helloText, worldText], {
selectionBackgroundColor: 'red',
backgroundColor: 'blue'
});
canvas.add(group);
A jsFiddle version can be found here.
As you can see from my code, I already tried the attribute backgroundColor yet it only affects contained objects. I would like to achieve an effect similar to selectionBackgroundColor.
Slight tweak, but this should do it for you (relevant code):
var text = new fabric.Group([helloText, worldText], {});
var textBoundingRect = text.getBoundingRect();
var background = new fabric.Rect({
top: textBoundingRect.top,
left: textBoundingRect.left,
width: textBoundingRect.width,
height: textBoundingRect.height,
fill: 'blue'
});
var group = new fabric.Group([background, text], {});
Your JSFiddle updated, https://jsfiddle.net/rekrah/92ss3d86/.

I need to create object group on undo function with fabricjs

I have two canvas objects (Rectungle and Triangle) in group.
Then i need to delete them, then return one of objects to is past position.
Here is two objects and group
var rect1 = new fabric.Rect(
{
id: 1, left: 10, top: 10, width: 100, height: 50, angle: 45, fill: 'red'
});
var tria1 = new fabric.Triangle({
id: 2, left: 200, top: 200, width: 100, height: 50, angle: 20, fill: 'yellow'
});
var objsGroup = new fabric.Group([rect1, tria1], {left: 100, top: 100});
And the whole fiddle here. I separate all steps by alerts https://jsfiddle.net/5js60oec/
UPDATE
Have the next problem. Lets assume that existing object group making some move and rotating before deleting. Then i have to restore it to point before rotating and moving. Thats, i think the main problem.
Actions
objsGroup.top = 200;
objsGroup.left = 200;
canvas.renderAll();
alert('4');
history.push(canvas.getActiveGroup());
objsGroup.setAngle(45);
canvas.renderAll();
Here the full fiddler: https://jsfiddle.net/nppaLetn/1/
For example I am deleting the last added path in my canvas like below
var lastItemIndex = (fabricCanvas.getObjects().length - 1);
var item = fabricCanvas.item(lastItemIndex);
if(item.get('type') === 'path') {
fabricCanvas.remove(item);
fabricCanvas.renderAll();
}
This may help you to accomplish your probs!!! I think so .

How to put canvas within canvas?

I just want to know what is the best way to use multiple canvas in a single page. These canvas can be overlapped on each other.
I tried to search this issue on different form, but wasn't able to find any helpful material. This is what we actually want to do(in the following image). There are 5 canvases, and we want all of them to be fully functional. We can add images, text and draw different things on selected canvas.
We are currently using fabricjs.
If that`s not possible, what is the best solution for achieving something like that ?
Thanks in advance!
Simply use CSS for that.
<div class="wrapper">
<canvas id="background_layer" class="canvas-layer" width="100" height="100"></canvas>
<canvas id="other_layer" class="canvas-layer" width="100" height="100"></canvas>
</div>
<style>
.wrapper { position: relative }
.canvas-layer {
position: absolute; left: 0; top: 0;
}
</style>
I am not sure what you are trying to achieve but you can refer to this Fiddle 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;
I hope this might help.

Stringify only one object in Fabric.js

I'm working with fabricJS. It's an awesome library for drawing.
var data = JSON.stringify(canvas);
Above command is used to get all the objects along with their properties in JSON format. However, I want to stringfy only an object.
For eg. Creating a rectangle using fabric
var rec = new fabric.Rect({
left: mouse_pos.x,
top: mouse_pos.y,
width: 75,
height: 50,
fill: 'white',
stroke: 'black',
strokeWidth: 3,
padding: 10
});
canvas.add(rec);
I want to get the JSON of only this object(rectangle) being added to the canvas and not the JSON of whole of the canvas being rendered.
May be something like var data = JSON.stringfy(rec);. Is there any way to do so because I want to send the latest object being added to the canvas over sockets and not the whole canvas object which will in turn consumes unnecessary bandwidth because of the JSON object size.
Yes you can! :)
var rect = new fabric.Rect({
left: mouse_pos.x,
top: mouse_pos.y,
width: 75,
height: 50,
fill: 'white',
stroke: 'black',
strokeWidth: 3,
padding: 10
});
canvas.add(rect);
// get last item
var obj = canvas.item(canvas.size() - 1);
// stringify only one object
var json = JSON.stringify(obj);
Can't you just turn your options object into JSON? Then you could send it through sockets, get the JSON on your receiver, parse it and apply into the fabric.Rect function to draw your rect there.
var options = {
left: mouse_pos.x,
top: mouse_pos.y,
width: 75,
height: 50,
fill: 'white',
stroke: 'black',
strokeWidth: 3,
padding: 10
};
var rec = new fabric.Rect(options);
canvas.add(rec);
var yourData = JSON.stringify(options); // turns your options object into JSON
// ...rest of your code, that sends it through sockets...

Categories