fabricjs limiting object drag - fabricjs

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

Related

Move cursor ghost when creating a group by selecting multiple existing objects on canvas

fabricjs_2.3.6
I am working on an editor that will allow the user to create textboxes images and rectangles dynamically and that portion of the project works great. All objects on the canvas will pre-existing before creating groups therefore I do not want to hard-code any fabrics. I also want to preserve the positional relation between the grouped items.
I am having a problem with creating groups by selecting 2 or more existing objects on the canvas to create a group. My code works with one exception, if the selected items are not in the upper left corner of the canvas when I create the group the grouped objects remain in the original location as desired but the move handle is in the upper left corner of the canvas. If you click the move cursor handle ghost and then click a blank area on the canvas the problem goes away and everything works fine after that.
I have had no luck searching for a solution possibly because I'm not sure what controls the move cursor's location on the canvas or if it is actually called the "move cursor".
Here is a picture from my jsfiddle after clicking the group button:
Here is a jsfiddle link to demonstrate the problem: https://jsfiddle.net/Larry_Robertson/xfrd278a/
Here is my code:
HTML
<span> Test 1: select both the line and the text objects with the mouse then click group, works great.</span>
<br/>
<span> Test 2: select both the line and the text objects with the mouse, move the selction to the center of the canvas then click group. This creates the problem where a ghost move handle is left behind in the upper left corner of the canvas. The move handle did not update to the correct position when the group was created. If you hover the mouse in the upper left of canvas you will see the move cursor. Click on the move cursor then click any blank part of the canvas then you can reselect the group and it moves properly. What am I doing wrong???</span>
<br/>
<button id="group">group</button>
<canvas id="c" height="300" width="500"></canvas>
JS
var canvas = new fabric.Canvas('c');
var text = new fabric.Text('hello world', {
fontSize: 30,
originX: 'left',
originY: 'top',
left: 0,
top: 0
});
var line = new fabric.Line([10, 10, 100, 100], {
stroke: 'green',
strokeWidth: 2
});
canvas.add(line);
canvas.add(text);
canvas.renderAll();
$('#group').on(("click"), function(el) {
var activeObject = canvas.getActiveObject();
var selectionTop = activeObject.get('top');
var selectionLeft = activeObject.get('left');
var selectionHeight = activeObject.get('height');
var selectionWidth = activeObject.get('width');
if (activeObject.type === 'activeSelection') {
var group = new fabric.Group([activeObject], {
left: 0,
top: 0,
originX: 'center',
originY: 'center'
});
canvas.add(group);
deleteSelectedObjectsFromCanvas();
canvas.setActiveObject(group);
group = canvas.getActiveObject();
group.set('top', selectionTop + (selectionHeight / 2));
group.set('left', selectionLeft + (selectionWidth / 2));
group.set('originX', 'center');
group.set('originY', 'center');
canvas.renderAll();
}
});
function deleteSelectedObjectsFromCanvas() {
var selection = canvas.getActiveObject();
var editModeDected = false;
if (selection.type === 'activeSelection') {
selection.forEachObject(function(element) {
console.log(element);
if (element.type == 'textbox') {
if (element.isEditing == true) {
//alert('At least one textbox is currently being edited. No objects were deleted');
editModeDected = true;
}
}
});
if (editModeDected == true) {
return false;
}
// Its okay to delete all selected objects
selection.forEachObject(function(element) {
console.log('removing: ' + element.type);
//element.set('originX',null);
//element.set('originY',null);
canvas.remove(element);
canvas.discardActiveObject();
canvas.requestRenderAll();
});
} else {
if (selection.isEditing == true && selection.type == 'textbox') {
//alert('Textbox is currently being edited. No objects were deleted');
} else {
canvas.remove(selection);
canvas.discardActiveObject();
canvas.requestRenderAll();
}
}
}
CSS
#c {
margin: 10px;
padding: 10px;
border: 1px solid black;
}
After making changes on code always do a setCoords on the object(s) that got changed.
Here is a one liner you can add after the renderAll to fix your issue:
...
group.set('originY', 'center');
canvas.renderAll();
canvas.forEachObject(function(o) {o.setCoords()});

canvas background turns black after rerender fabric.js

I have the following situation: I draw the canvas with a background image and this works:
var canvasSection = new fabric.Canvas('buildSection', {
selectionColor: 'rgba(94,213,94,0.2)',
width: widthS,
height: heightS,
hoverCursor: 'pointer',
backgroundColor: ({
source: 'image/bordenconfigurator/background/background1.png',
repeat: 'repeat'
})
});
But then I need to clear canvas and to redraw it. at this point when I try to set background, instead of an image I see a black screen. In console I can see that the background image is set however. This is the code I'm trying:
$('#cForm').on('change', function() {
if ($(this).val() == 'rectLandsc') {
widthS = 600;
heightS = 400;
} else if ($(this).val() == 'rectPortr') {
....
}
canvasSection.clear();
canvasSection.set({
fill: 'rgba(0,0,0,0)',
// backgroundColor: ({source: 'image/bordenconfigurator/background/background1.png', repeat: 'repeat'})
});
//canvasSection.backgroundColor({source: 'image/bordenconfigurator/background/background.png', repeat:'repeat'});
canvasSection.setBackgroundColor({
source: 'image/bordenconfigurator/background/background.png'
}),
canvasSection.renderAll.bind(canvas);
drawBaseForm();
canvasSection.renderAll();
console.log(canvasSection);
});
Is it a kind of a bug? Or am I doing something wrong? Could anyone help me out here?
canvas.setBackgroundColor({
source: 'image/bordenconfigurator/background/background1.png',
repeat: 'repeat'
}, canvas.renderAll.bind(canvas));
setBackgroundColor first parameter is color/pattern and second parameter is callback function.
DEMO
var canvas = new fabric.Canvas('c');
canvas.setBackgroundColor({
source: 'http://fabricjs.com/assets/pug_small.jpg'
}, canvas.renderAll.bind(canvas));
function clearCanvas(){
var background = canvas.backgroundColor;
canvas.clear();
setTimeout(function(){
canvas.setBackgroundColor(background,canvas.renderAll.bind(canvas));
},2000)
}
canvas{
border: 2px dotted black;
}
<script src="https://rawgithub.com/kangax/fabric.js/master/dist/fabric.js"></script>
<button onclick='clearCanvas()'>clear</button>
<canvas id="c" width="500" height="500"></canvas>

FabricJS double click new techniques?

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>

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.

Fabric JS how to address a SVG

I have loaded a SVG on the canvas. Everything works, but I have difficulties to address the object.
Here is the code:
fabric.loadSVGFromURL("images/Achse3.svg",function(objects)
{
var achse1 = new fabric.PathGroup(objects, {
left: 127,
top: 70,
opacity: 0.5,
scaleX: 1.25,
scaleY: 1.25
});
canvas.add(achse1);
//canvas.renderAll();
});
I tried to address the SVG via canvas.item, but the results are very strange. Depending on the value in the sample:
canvas.item(11).set('top',this.value);
The items change. How can I address the item I loaded. I tried it with
canvas.achse1.set('top',this.value);
But this is not working.
Since you have a reference to your PathGroup, you have just to set the related properties of it.
Try yourself the runnable snippet below if it meets your needs.
var canvas = new fabric.Canvas('c'), svg_group;
fabric.loadSVGFromURL("https://upload.wikimedia.org/wikipedia/commons/a/a9/Olympic_rings_with_white_rims.svg", function(objects) {
svg_group = new fabric.PathGroup(objects, {
left: 0,
top: 0,
opacity: 1,
scaleX: .1,
scaleY: .1
});
canvas.add(svg_group);
$('#b').on('click', () => {
svg_group.set('top', 100);
canvas.renderAll();
})
});
canvas {
border: 1px dashed #333;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.4/fabric.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id='c' width=300 height=150></canvas>
<button id='b'>move it</button>
Found the problem. I used VAR to declare the variable within the loadSVG function. So the variable was just local within the function. The declaration without VAR did the job. Now the variable is global and can be addressed from outside the function.

Resources