How to trigger or receive events of objects in the group? - javascript

I use Fabric.js to develop a ppt editor in online. I have a problem, a group A has B and C, I can listen Events of A, but I need also listen events of it's A and B. How to do it?

you need to set subTargetCheck true for group object and then you can access subtargets from event which contains array of subTargets[]
var canvas = new fabric.Canvas('c');
var rect = new fabric.Rect({
width:100,height:100,left:10,top:10,fill:'red'
});
var circle = new fabric.Circle({
left:150,top:10,fill:'green',radius:50
});
var group = new fabric.Group([rect,circle],{
left:20,
top:20,
subTargetCheck: true
});
canvas.add(group);
group.on('mousedown',onMouseDown);
function onMouseDown(option){
option.subTargets[0] && console.log(option.subTargets[0].type)
}
canvas{
border: 1px solid #000;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="400" height="400"></canvas>

Related

Fabricjs, on loadFromJSON the textBox is not gettin editable

I am facing an issue while using FabricJS
On using method loadFromJSON, It render the data but textBox text is not editable.
Any help?
Thanks!
Here is reproducible code
jsfiddle
You need to change the object type from text to i-text.
// Do some initializing stuff
fabric.Object.prototype.set({
transparentCorners: false,
cornerColor: 'rgba(102,153,255,0.5)',
cornerSize: 12,
padding: 5
});
// initialize fabric canvas and assign to global windows object for debug
var canvas = window._canvas = new fabric.Canvas('c');
var json = '{"version":"3.6.3","objects":[{"type":"i-text","version":"3.6.3","originX":"left","originY":"top","left":100,"top":100,"width":579,"height":114.72,"fill":"rgb(0,0,0)","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"clipTo":null,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","transformMatrix":null,"skewX":0,"skewY":0,"text":"You have probably serialized your objects with one (or more) of them having a custom property or you created and serialized a new custom class altogether. Have a look here to get a better idea about the rules of Canvas serialization: http://fabricjs.com/docs/fabric.Canvas.html#toJSON;","fontSize":18,"fontWeight":"normal","fontFamily":"Montserrat","fontStyle":"normal","lineHeight":1.16,"underline":false,"overline":false,"linethrough":false,"textAlign":"left","textBackgroundColor":"","charSpacing":0,"minWidth":20,"splitByGrapheme":false,"styles":{}}],"background":"#ffffff"}'
canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), function(o, object) {
//fabric.log(o, object);
});
canvas {
border: 1px solid #999;
}
<script src="https://rawgithub.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="600" height="600"></canvas>
http://jsfiddle.net/moshfeu/mtyhreds/

How do I replace an image on a canvas onclick?

I'm using fabricjs to place an image on a canvas. I'd like to learn what I need to do to to replace the already drawn image with another one from a url onclick.
My limited understanding has me thinking I need to maybe set a var for each image but I'm not sure how to connect that with the fabricjs canvas. I've spent the last few hours trying different things without any luck.
I tried submitting a similar question but deleted it because I thought maybe it was too complicated an ask. Any help would be greatly appreciated. Here's my code now:
var canvas = new fabric.Canvas('c');
fabric.Image.fromURL('http://lorempixel.com/400/400/', function(img) {
var oImg = img.set({
selectable: false
})
canvas.add(oImg).renderAll();
});
canvas {
border: 1px solid #dddddd;
border-radius: 3px;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<canvas id="c" width="400" height="400"></canvas>
This is how you could achieve that :
var canvas = new fabric.Canvas('c');
var image, isImageLoaded;
fabric.Image.fromURL('//lorempixel.com/200/200/', function(img) {
isImageLoaded = true;
image = img.set({
selectable: false
})
canvas.add(image).renderAll();
});
function replaceImage(imgUrl) {
if (!isImageLoaded) return; //return if initial image not loaded
var imgElem = image._element; //reference to actual image element
imgElem.src = imgUrl; //set image source
imgElem.onload = () => canvas.renderAll(); //render on image load
}
canvas.on('mouse:down', function() {
replaceImage('//lorempixel.com/200/200/');
});
canvas {
border: 1px solid #dddddd;
border-radius: 3px;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<canvas id="c" width="200" height="200"></canvas>
PS: apology for not giving that much explanation.
You can do this much simpler. Don't see why you'd use an extra lib for something like this. I wrote something small in plain JS which hopefully helps you further.
document.getElementById("image").onclick = function(){
document.getElementById("image").src = "http://lorempixel.com/400/400/";
};
<img id="image" src="http://lorempixel.com/400/400/">
var canvas = new fabric.Canvas('c');
var oImg;
loadImage();
function loadImage(){
fabric.Image.fromURL('https://lorempixel.com/400/400/', function(img) {
oImg && canvas.remove(oImg); //If image element alread added to canvas remove it.
oImg = img.set({
selectable: false
})
canvas.add(oImg); //add new image to canvas
oImg.on('mousedown',onImgMouseDown); //add mouse down lisner to image
});
}
function onImgMouseDown(){
loadImage();
}
canvas {
border: 1px solid #dddddd;
border-radius: 3px;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<canvas id="c" width="400" height="400"></canvas>
Alternative you add mousedown listener to image , and check if element added ,then remove it from canvas. then add new one to canvas.

Adding canvas inside another canvas: obj.setCoords is not a function( fabric js)

Started using fabric.js and trying to add a canvas inside another canvas, so that the top canvas stays constant and I'll add objects to inner canvas.
Here is the snippet of adding a canvas to another canvas.
canvas = new fabric.Canvas('artcanvas');
innerCanvas = new fabric.Canvas("innerCanvas");
canvas.add(innerCanvas);
and my html looks like this
<canvas id="artcanvas" width="500" height="500"></canvas>
<canvas id="innerCanvas" width="200" height="200" ></canvas>
Once adding these successfully, what I am going to do is , add the coordinates to the inner canvas, so that it looks like one on another to the end user.
However, ran into the below error for the tried code
Uncaught TypeError: obj.setCoords is not a function
at klass._onObjectAdded (fabric.js:6894)
at klass.add (fabric.js:231)
at main.js:60
at fabric.js:19435
at HTMLImageElement.fabric.util.loadImage.img.onload (fabric.js:754)
_onObjectAdded # fabric.js:6894
add # fabric.js:231
(anonymous) # main.js:60
(anonymous) # fabric.js:19435
fabric.util.loadImage.img.onload # fabric.js:754
Looking at the error message, just went to the line of error and here is what I found in chrome console
Can someone point the mistake in my codes ?
After going through no.of discussions and internet solutions, for time being I am using Fabric Rectangle as a clipper and setting it's boundaries so user can be able to drop/play with in that particular clipper.
Dotted red(image below) is my clipper and now I can bound the dropping and below is the code to add an image with a clipper.
function addImageToCanvas(imgSrc) {
fabric.Object.prototype.transparentCorners = false;
fabric.Image.fromURL(imgSrc, function(myImg) {
var img1 = myImg.set({
left: 20,
top: 20,
width: 460,
height: 460
});
img1.selectable = false;
canvas.add(img1);
var clipRectangle = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 150,
top: 150,
width: 200,
height: 200,
fill: 'transparent',
/* use transparent for no fill */
strokeDashArray: [10, 10],
stroke: 'red',
selectable: false
});
clipRectangle.set({
clipFor: 'layer'
});
canvas.add(clipRectangle);
});
}
Now while appending any image/layer to the canvas, I bind that image/layer/text to the clipper I created.
function addLayerToCanvas(laImg) {
var height = $(laImg).height();
var width = $(laImg).width();
var clickedImage = new Image();
clickedImage.onload = function(img) {
var pug = new fabric.Image(clickedImage, {
width: width,
height: height,
left: 150,
top: 150,
clipName: 'layer',
clipTo: function(ctx) {
return _.bind(clipByName, pug)(ctx)
}
});
canvas.add(pug);
};
clickedImage.src = $(laImg).attr("src");
}
And the looks like, after restriction of bounds,
Here is the fiddle I have created with some static image url.
https://jsfiddle.net/sureshatta/yxuoav39/
So I am staying with this solution for now and I really feel like this is hacky and dirty. Looking for some other clean solutions.
As far as I know you can't add a canvas to another canvas - you're getting that error as it tries to call setCoords() on the object you've added, but in this case it's another canvas and fabric.Canvas doesn't contain that method (see docs). I think a better approach would be to have two canvases and position them relatively using CSS - see this simple fiddle
HTML
<div class="parent">
<div class="artcanvas">
<canvas id="artcanvas" width="500" height="500"></canvas>
</div>
<div class="innerCanvas">
<canvas id="innerCanvas" width="200" height="200" ></canvas>
</div>
</div>
CSS
.parent {
position: relative;
background: black;
}
.artcanvas {
position: absolute;
left: 0;
top: 0;
}
.innerCanvas {
position: absolute;
left: 150px;
top: 150px;
}
JS
$(document).ready(function() {
canvas = new fabric.Canvas('artcanvas');
innerCanvas = new fabric.Canvas("innerCanvas");
var rect = new fabric.Rect({
fill: 'grey',
width: 500,
height: 500
});
canvas.add(rect);
var rect2 = new fabric.Rect({
fill: 'green',
width: 200,
height: 200
});
innerCanvas.add(rect2);
})
To handle the object serialization, you can do something like this:
var innerObjs = innerCanvas.toObject();
console.dir(innerObjs);
var outerObjs = canvas.toObject();
innerObjs.objects.forEach(function (obj) {
obj.left += leftOffset; // offset of inner canvas
obj.top += topOffset;
outerObjs.objects.push(obj);
});
var json = JSON.stringify(outerObjs);
This will then give you the JSON for all objects on both canvases
I have no understanding why you want to do this thing, but to put a canvas inside another canvas, you have one simple way:
canvas = new fabric.Canvas('artcanvas');
innerCanvas = new fabric.Canvas("innerCanvas");
imageContainer = new fabric.Image(innerCanvas.lowerCanvasEl);
canvas.add(imageContainer);
Then depending what you want to do, you may need additional tweaks, but this should work out of the box.
Don't create a canvas
Most objects in fabric (from my limited experience) are at some point converted to a canvas. Creating an additional fabric canvas to manage a group of objects is kind of pointless as you are just adding overhead and mimicking fabrics built in groups object.
Fabric objects are basically DOM canvases wrappers.
The following example shows how fabric uses a canvas to store the content of a group. The demo creates a group and adds it to the fabric canvas, then gets the groups canvas and adds it to the DOM. Clicking on the group's canvas will add a circle. Note how it grows to accommodate the new circles.
const radius = 50;
const fill = "#0F0";
var pos = 60;
const canvas = new fabric.Canvas('fCanvas');
// create a fabric group and add two circles
const group = new fabric.Group([
new fabric.Circle({radius, top : 5, fill, left : 20 }),
new fabric.Circle({radius, top : 5, fill, left : 120 })
], { left: 0, top: 0 });
// add group to the fabric canvas;
canvas.add(group);
// get the groups canvas and add it to the DOM
document.body.appendChild(group._cacheContext.canvas);
// add event listener to add circles to the group
group._cacheContext.canvas.addEventListener("click",(e)=>{
group.addWithUpdate(
new fabric.Circle({radius, top : pos, fill : "blue", left : 60 })
);
canvas.renderAll();
pos += 60;
});
canvas {
border : 2px solid black;
}
div {
margin : 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.13/fabric.min.js"></script>
<div>Fabric's canvas "canvas = new fabric.Canvas('fCanvas');"</div>
<canvas id="fCanvas" width="256" height="140"></canvas>
<div>Fabric group canvas below. Click on it to add a circle.</div>
Use a group rather than a new instance of a fabric canvas.
As you can see a canvas is generated for you. Adding another fabric canvas (Note that a fabric canvas is not the same as a DOM canvas) will only add more work for fabric to do, which already has a lot of work to do.
You are best of to use a group and have that hold the content of the other fabric object you wish to shadow. That would also contain its content in a group.
Just an image
And just a side not, a DOM canvas is an image and can be used by fabric just as any other image. It is sometimes better to do the rendering directly to the canvas rather than via fabric so you can avoid rendering overheads that fabric needs to operate.
To add a DOM canvas to fabric just add it as an image. The border and text are not fabric object, and apart from the code to render them take up no memory, and no additional CPU overhead that would be incurred if you used a fabric canvas and objects.
const canvas = new fabric.Canvas('fCanvas');
// create a standard DOM canvas
const myImage = document.createElement("canvas");
// size it add borders and text
myImage.width = myImage.height = 256;
const ctx = myImage.getContext("2d");
ctx.fillRect(0,0,256,256);
ctx.fillStyle = "white";
ctx.fillRect(4,4,248,248);
ctx.fillStyle = "black";
ctx.font = "32px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("The DOM canvas!",128,128);
// use the canvas to create a fabric image and add it to fabrics canvas.
canvas.add( new fabric.Image(myImage, {
left: (400 - 256) / 2,
top: (400 - 256) / 2,
}));
canvas {
border : 2px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.13/fabric.min.js"></script>
<canvas id="fCanvas" width="400" height="400"></canvas>
innerCanvas.setCoords(); ìs a function, but you need it only after you set the coordinates. Or more precise, set these four elements:
innerCanvas.scaleX = 1;
innerCanvas.scaleY = 1;
innerCanvas.left = 150;
innerCanvas.top = 150;
innerCanvas.setCoords();
canvas.renderAll();

How to add image to fabric canvas?

I want to add images/icons to my fabric canvas. The code given on the Fabric demo is not working.
fabric.Image.fromURL('my_image.png', function(oImg) {
canvas.add(oImg);
});
This just makes my entire canvas blank.
I want to add icons as clickable elements which respond to events.
I have done a jsfiddle that loads a jpg image on the canvas, by using the
fabric.Image.fromURL() function and the 'mouse:down' event. Inside the mouse:down i check if the user clicks on an object or just on the canvas.
here is a snippet of the javascript:
var canvas = new fabric.Canvas('c');
canvas.backgroundColor = 'yellow';
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:down',function(event){
if(canvas.getActiveObject()){
alert(event.target);
}
})
but my example also works ok ,if i dont use the extra variable:
fabric.Image.fromURL('http://fabricjs.com/assets/pug_small.jpg', function(myImg) {
canvas.add(myImg);
});
if you need more fabric events you can take a look here : http://fabricjs.com/events/
jsfiddle : https://jsfiddle.net/tornado1979/5nrmwtxu/
Hope helps , good luck.
This code is working properly but you need to use fabric.js file.
You also need to use fabric.js CDN file in your header.
Fabric.js CDN->
var canvas = new fabric.Canvas('drawarea');
var imgElement = document.getElementById('my-image');
var imgInstance = new fabric.Image(imgElement, {
left: 100,
top: 100,
angle: 0,
opacity: 0.75,
width:300,
height:300
});
canvas.add(imgInstance);
#my-image{display:none;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.22/fabric.min.js"></script>
<canvas width="800" height="800" id="drawarea" style="border: 1px solid red;float: right"> </canvas>
<img src="https://via.placeholder.com/300.png" id="my-image" width="500px" height="500px">

Add free drawing result on another canvas in fabricjs

There are two canvas instance, the smaller 'd' for free drawing, the bigger 'c' is to be added on. How can I only add the boundary of drawing on 'd' to 'c', not the entire 'd' canvas area with lots of empty area to 'c'?
Fiddle
I hope the code can explains more clear. thanks.
HTML
<canvas width="300" height="300" id="c"></canvas>
<canvas width="150" height="150" id="d"></canvas>
<button id="btn">add To c</button>
JavaScript
var c = new fabric.Canvas('c')
var d = new fabric.Canvas('d', {
isDrawingMode: true
})
document.getElementById('btn').onclick = addDrawToBig
function addDrawToBig(){
var svg = d.toSVG()
fabric.loadSVGFromString(svg,function(objects, options) {
var obj = fabric.util.groupSVGElements(objects, options);
c.add(obj).centerObject(obj).setActiveObject(obj);
});
}
var circle = new fabric.Circle({
stroke: 'red',
radius: 15,
left: 15,
top: 15
})
d.add(circle)
d.renderAll()
addDrawToBig()
finally i find the answer to my question, just use fabric.Group and .clone() method:
function addDrawToBig(){
var group = [ ]
d.getObjects().forEach(function(path){
path.clone(function(copy){
group.push(copy);
});
});
group = new fabric.Group( group )
c.add(group).centerObject(group).setActiveObject(group);
}
fiddle here
Special thanks to #AndreaBogazzi, wish a good day!
i updated your fiddle with something that works better.
Fiddle
Canvas.toSVG() is not meant to be used as a export import function.
I suggest you to cycle throught the canvas objects and clone all of them in the new canvas.
This demo is not really cloning them, just copying them on the other canvas.
Without cloning you have the side effect that if you modify something in one canvas the modification get mirrored in the other.
You should add
c.add(ao[i].clone());
But for some reason free drawing path .clone() is failing. ( will look in this next days to see if it is a bug or a misusage )
var c = new fabric.Canvas('c')
var d = new fabric.Canvas('d', {
isDrawingMode: true
})
document.getElementById('btn').onclick = addDrawToBig
function addDrawToBig(){
var ao = d.getObjects();
console.log(ao);
for(var i in ao) {
c.add(ao[i]);
}
}
var circle = new fabric.Circle({
stroke: 'red',
radius: 15,
left: 15,
top: 15
})
d.add(circle)
d.renderAll()
addDrawToBig()
Hope it helps.

Categories