FabricJS double click new techniques? - fabricjs

could someone please point me in the direction for how to enable double click on fabric images? i came across this solution
FabricJS double click on objects
I am trying to not use FabicjsEx
but i am unable to get anything to work correctly. can someone please let me know the best way to accomplish this?

The best way to accomplish this, is to use fabric.util.addListener method.
Using that you could add a double click event for the canvas element and to restrict it to any particular object ( ie. image ), you would have to check whether you clicked on an image object or not before performing any action.
ᴅᴇᴍᴏ
var canvas = new fabric.Canvas('canvas');
// add image
fabric.Image.fromURL('https://i.imgur.com/Q6aZlme.jpg', function(img) {
img.set({
top: 50,
left: 50
})
img.scaleToWidth(100);
img.scaleToHeight(100);
canvas.add(img);
});
// add rect (for demo)
var rect = new fabric.Rect({
left: 170,
top: 50,
width: 100,
height: 100,
fill: '#07C'
});
canvas.add(rect);
// mouse event
fabric.util.addListener(canvas.upperCanvasEl, 'dblclick', function(e) {
if (canvas.findTarget(e)) {
const objType = canvas.findTarget(e).type;
if (objType === 'image') {
alert('double clicked on a image!');
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.9/fabric.js"></script>
<canvas id="canvas" width="320" height="200"></canvas>

Related

fabricjs limiting object drag

Referring to the code below, I can drag the red line back and forth within the canvas box and have it stop at the left limit. But the line doesn't stop at the right limit - it disappears beyond the box and reappears if I drag left. I have looked at fabricjs demos (Standalone Controls, PerPixel Drag and Drop, Stickman) and notice that it is possible to drag objects "out of sight" in a similar manner. Is this a characteristic of fabricjs, or is there some way I can fix my code so that the red line cannot be dragged beyond the right limit?
var canvas = new fabric.Canvas('cnvs');
var leftLimit = 20;
var rightLimit = 400;
var redLine = new fabric.Line([leftLimit, 0, leftLimit, canvas.height], {
stroke: 'red',
strokeWidth: 2,
borderColor: 'white',
hasControls: false
});
canvas.add(redLine);
canvas.renderAll();
canvas.on('object:moving', function(e) {
redLine = e.target;
redLine.set({
top: 0
});
if (redLine.left < leftLimit) {
redLine.set({
left: leftLimit
});
}
if (redLine > rightLimit) {
redLine.set({
left: rightLimit
});
}
canvas.renderAll();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.3/fabric.js"></script>
<canvas id="cnvs" width="500" height="200" style="border:1px solid #000000"></canvas>
I'm not sure if you made a typo or not, but (redLine > rightLimit) makes no sense. You should compare left:
if (redLine.left > rightLimit) {
redLine.set({ left: rightLimit });
}
Or, since your line has some width to it, you can make it stop without ever overlapping the boundary:
if (redLine.left + redLine.strokeWidth > rightLimit) {
redLine.set({ left: rightLimit - redLine.strokeWidth });
}

Can not restore saved multi selection in FabricJS

Can you help me figure out what is going on here.
Basically I want to use FabricJS with ReactJS and Redux. I want to store the currently selected objects in redux store. But it seems it has a weird behavior when it comes to saving the active objects of multi selection.
let selectedObjects = null;
let canvas = new fabric.Canvas('canvas-area', {
preserveObjectStacking: true,
width: 500,
height: 500
});
canvas.add(new fabric.Rect({
left: 100,
top: 100,
fill: 'blue',
width: 100,
height: 100
}));
canvas.add(new fabric.Circle({
radius: 50,
fill: 'green',
left: 200,
top: 200
}));
canvas.renderAll();
// =======================
document.querySelector('#save-selection').addEventListener('click', ()=> {
//selectedObjects = Object.assign({}, canvas.getActiveObject());
selectedObjects = canvas.getActiveObject();
canvas.discardActiveObject();
canvas.renderAll();
});
document.querySelector('#apply-saved-selection').addEventListener('click', ()=> {
canvas.setActiveObject(selectedObjects);
canvas.renderAll();
});
<html>
<body>
<button id='save-selection'>Save Selection</button>
<button id='apply-saved-selection'>Apply Saved Selection</button>
<canvas id='canvas-area'></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.3/fabric.min.js"></script>
</body>
</html>
Basically the way you use the code snippet above are:
Use Case 1 : Single selected object
Select a single object.
Click Save Selection ( it will save the current selected object and clear current canvas selection ).
Click Apply Saved Selection.
Drag the currently selected object, the selection and the selected object stays in synced and the app works as expected.
Use Case 2 : Multiple selection
Select multiple objects.
Click Save Selection.
Click Apply Saved Selection.
Drag the currently selected objects, as you can see, only the selection is dragged, the actual selected objects are left behind (unsynced).
Can you help me figure out how to properly saved the selected object/s for later use?
Thanks in advance
Note:
I'm using latest FabricJS (2.3.3)
Update:
It is not wise to store selected elements in redux store. It is very hard to reload saved selected elements specially when we handle grouped selection, grouped objects, clipped images, etc...
Bottom Line, Do not save selected elements in redux store.
==================
Update:
My answer below have a flaw, and that is the rotation angle of the selection is not preserved when saved rotated selection is being loaded.
Still looking for a much better answer than what I currently have.
==================
Ok so it seems we need to have different approach in reloading multi selected objects.
Summary
We need to create a new selection using the exact objects in the canvas that correspond to the objects inside the saved selection.
// Extend fabric js so that all objects inside the canvas
// will have the custom id attribute
// We will use this later to determine which is which
fabric.Object.prototype.id = undefined;
let selectedObjects = null;
let canvas = new fabric.Canvas('canvas-area', {
preserveObjectStacking: true,
width: 500,
height: 500
});
canvas.add(new fabric.Rect({
id: 'unique-id-01',
left: 100,
top: 100,
fill: 'blue',
width: 100,
height: 100
}));
canvas.add(new fabric.Circle({
id: 'unique-id-02',
radius: 50,
fill: 'green',
left: 200,
top: 200
}));
canvas.add(new fabric.Rect({
id: 'unique-id-03',
left: 300,
top: 300,
fill: 'red',
width: 100,
height: 100
}));
canvas.renderAll();
// =======================
document.querySelector('#save-selection').addEventListener('click', ()=> {
//selectedObjects = Object.assign({}, canvas.getActiveObject());
selectedObjects = canvas.getActiveObject();
canvas.discardActiveObject();
canvas.renderAll();
});
document.querySelector('#apply-saved-selection').addEventListener('click', ()=> {
if (selectedObjects.type === 'activeSelection') {
let canvasObjects = canvas.getObjects(); // Get all the objects in the canvas
let selObjs = [];
// Loop through all the selected objects
// Then loop through all the objects in the canvas
// Then check the id of each objects if they matched
// Then store the matching objects to the array
for (let so of selectedObjects._objects) {
for (let obj of canvasObjects) {
if (obj.id === so.id) {
selObjs.push(obj); // Store the canvasObjects item, not the selectedObjects item.
break;
}
}
}
// Create a new selection instance using the filtered objects above
let selection = new fabric.ActiveSelection(selObjs, {
canvas: canvas
});
canvas.setActiveObject(selection); // Profit
} else { // Single object selected, load directly
canvas.setActiveObject(selectedObjects);
}
canvas.renderAll();
});
<html>
<body>
<button id='save-selection'>Save Selection</button>
<button id='apply-saved-selection'>Apply Saved Selection</button>
<canvas id='canvas-area'></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.3/fabric.min.js"></script>
</body>
</html>
We need to extend fabric js and add custom id attribute.
We will use this later to compare between the objects inside the fabric js instance and the objects inside the saved selection.
Loop through the fabric js instance objects and the saved selection objects and determine which of the objects in fabric js instance are selected by using the custom id we added earlier.
Create new selection instance, new fabric.ActiveSelection, using the filtered objects in step 2.
Set the fabric.ActiveSelection instance as the activeObject of the fabric js instance.
Profit
Fabric JS Selection Handling Reference

Fabricjs How to scale object but keep the border (stroke) width fixed

I'm developing a diagram tool based on fabricjs. Our tool has our own collection of shape, which is svg based. My problem is when I scale the object, the border (stroke) scale as well. My question is: How can I scale the object but keep the stroke width fixed. Please check the attachments.
Thank you very much!
Here is an easy example where on scale of an object we keep a reference to the original stroke and calculate a new stroke based on the scale.
var canvas = new fabric.Canvas('c', { selection: false, preserveObjectStacking:true });
window.canvas = canvas;
canvas.add(new fabric.Rect({
left: 100,
top: 100,
width: 50,
height: 50,
fill: '#faa',
originX: 'left',
originY: 'top',
stroke: "#000",
strokeWidth: 1,
centeredRotation: true
}));
canvas.on('object:scaling', (e) => {
var o = e.target;
if (!o.strokeWidthUnscaled && o.strokeWidth) {
o.strokeWidthUnscaled = o.strokeWidth;
}
if (o.strokeWidthUnscaled) {
o.strokeWidth = o.strokeWidthUnscaled / o.scaleX;
}
})
canvas {
border: 1px solid #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.4/fabric.min.js"></script>
<canvas id="c" width="600" height="600"></canvas>
There is a property called: strokeUniform
Use it like this
shape.set({stroke: '#f55b76', strokeWidth:2, strokeUniform: true })
I have found what feels like an even better solution, works really well with SVG paths.
You can override fabricjs' _renderStroke method and add ctx.scale(1 / this.scaleX, 1 / this.scaleY); before ctx.stroke(); as shown below.
fabric.Object.prototype._renderStroke = function(ctx) {
if (!this.stroke || this.strokeWidth === 0) {
return;
}
if (this.shadow && !this.shadow.affectStroke) {
this._removeShadow(ctx);
}
ctx.save();
ctx.scale(1 / this.scaleX, 1 / this.scaleY);
this._setLineDash(ctx, this.strokeDashArray, this._renderDashedStroke);
this._applyPatternGradientTransform(ctx, this.stroke);
ctx.stroke();
ctx.restore();
};
You may also need to override fabric.Object.prototype._getTransformedDimensions to adjust the bounding box to account for the difference in size.
Also a more complete implementation would probably add a fabric object property to conditionally control this change for both overridden methods.
Another way is to draw a new object on scaled and remove the scaled one.
object.on({
scaled: function()
{
// store new widht and height
var new_width = this.getScaledWidth();
var new_height = this.getScaledHeight();
// remove object from canvas
canvas.remove(this);
// add new object with same size and original options like strokeWidth
canvas.add(new ...);
}
});
Works perfect for me.

What can I get a handle on the target of a fabric js event?

In this fabricjs tutorial section it says putting listeners right on an object like so
var rect = new fabric.Rect({ width: 100, height: 50, fill: 'green' });
rect.on('selected', function() {
console.log('selected a rectangle');
});
That is fine, but I don't want to log a string to the console. I want to manipulate the rectangle. Specifically the rectangle isn't selectable by default and based on application state, I need to make it selectable.
I can't figure out how to get a handle on the rectangle from inside the event handler. How to do that escapes me.
In the example you gave, you have attached the event to the object itself. So you can access the object using this or the variable name (which is rect in your case). Here is an example
var rect = new fabric.Rect({ width: 100, height: 50, fill: 'green' });
rect.on('selected', function() {
console.log(this);
this.set({width:200});
});
Alternatively, if you attach it to the canvas so that it listens to all the object:selected events, you can access the object at which the event occurred like this:
var rect = new fabric.Rect({ width: 100, height: 50, fill: 'green' });
canvas.on('object:selected', function (e) {
console.log('selected a rectangle');
e.target.set({width:200});
});

Text always displays under image in Fabric.js

I have the following problem. I have been using Fabric.js for a project and find it easy to use. However I have the following problem that i just don't seem able to solve. I am probably missing the obvious but its time to ask for help.
I can't get a text layar to display ontop of a loaded image. here is the code :
<canvas id="c" height=600 width=800></canvas>
var canvas = new fabric.StaticCanvas('c');
fabric.Image.fromURL('http://mozorg.cdn.mozilla.net/media/img/products/badge- firefoxos.jpg?2013-06', function(img) {
var imgX = img.set({
top: 0,
left: 0,
});
canvas.add(imgX);
});
var text = new fabric.Text("This text always gets burried", {
top : 0,
left : 0,
fontSize : 50,
fontFamily : 'Delicious_500'
});
canvas.add(text);
canvas.BringToFront(text);
canvas.renderAll();
I also created a fiddle : http://jsfiddle.net/NkqWL/9/
Any advice is greatly appreciated.
I have updated your fiddle
Fiddle Link:- Click Here
Send your image to back using canvas.sendToBack(imgX);
fabric.Image.fromURL('http://mozorg.cdn.mozilla.net/media/img/products/badge-firefoxos.jpg?2013-06', function(img) {
var imgX = img.set({
top: 0,
left: 0,
});
canvas.add(imgX);
canvas.sendToBack(imgX);
});

Resources