FabricJS. How to add dimensions displaying in controls? - fabricjs

How to add dimensions displaying in controls like on screenshot?

Create a text object and set it hidden. And when you are scalling object set the scale width and height to hidden text and make visible. On object modified set visible false to the text.
DEMO
var canvas = new fabric.Canvas("c");
canvas.setHeight(200);
canvas.setWidth(300);
var dimText = new fabric.Text("demo", {
fontSize: 15,
visible: false
});
canvas.add(dimText);
var circle = new fabric.Circle({
left: 15,
top: 15,
radius: 20,
fill:'',
stroke: 'red'
});
canvas.add(circle);
var text = new fabric.Text("2018", {
padding: 30,
lineHeight: 30
});
canvas.add(text);
canvas.centerObject(text);
text.setCoords();
canvas.on('object:scaling', function(option) {
var object = option.target;
var pointer = canvas.getPointer(option.e);
dimText.set({
left: pointer.x - 20,
top: pointer.y - 20,
text: parseInt(object.width * object.scaleX) + 'x' + parseInt(object.height * object.scaleY),
visible: true
})
});
canvas.on('object:modified', function(option) {
dimText.set('visible', false);
});
canvas {
border: 1px solid #dddddd;
margin-top: 10px;
border-radius: 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.7/fabric.min.js"></script>
<canvas id="c"></canvas>

Related

fabric.js 3.6 - Bounding box for group doesn't update when grouped objects are changed

The problem I'm having with fabric.js seems simple, but I've been searching for a solution without any luck, so here I am.
I create a simple group to draw an arrow, consisting of a line object and a rotated triangle placed at its end point. This works fine, but when I dynamically update the stroke width of the line and the width/height of the triangle, the group's bounding box doesn't update to match the new dimensions. Here's an image of the result:
Here's a live demo. Just drag the slider to change the stroke width and triangle size and you'll see the issue.
let canvas = new fabric.Canvas(document.querySelector('canvas'), {
backgroundColor: 'white'
}),
object,
lineCoords = [100, 75, 250, 75],
strokeWidth = parseFloat(document.querySelector('.strokewidth').value),
createArrow = () => {
return new fabric.Group(
[
new fabric.Line(lineCoords, {
left: lineCoords[0],
top: lineCoords[1],
stroke: 'red',
strokeWidth: strokeWidth,
strokeLineCap: "round",
}),
new fabric.Triangle({
width: strokeWidth + 22,
height: strokeWidth + 22,
fill: 'red',
left: lineCoords[0] + 150 + strokeWidth + 22 + ((strokeWidth + 22) / 2) - 14,
top: lineCoords[1] - (Math.floor((strokeWidth + 22) / 2)) + (Math.floor(strokeWidth / 2)),
angle: 90,
})
],
{ objectCaching: false }
)
}
let arrow = createArrow()
canvas.add(arrow)
canvas.setActiveObject(arrow)
canvas.renderAll()
$('body').on('input change', '.strokewidth', e => {
if (object = canvas.getActiveObject()) {
let value = parseFloat(e.target.value),
[ line, triangle ] = object.getObjects()
line.set({
strokeWidth: value
})
triangle.set({
width: value + 22,
height: value + 22,
left: line.left + line.width + value + 22 + ((value + 22) / 2) - 14,
top: line.top - (Math.floor((value + 22) / 2)) + (Math.floor(value / 2))
})
canvas.renderAll()
}
})
body {
background: #f0f0f0;
padding: 0;
margin: 0;
}
canvas {
border: 1px solid lightgray;
}
div {
margin: 0.5em 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.0/fabric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas width="400" height="150"></canvas>
<div>
<input type="range" class="strokewidth" min="1" max="40" value="5">
</div>
I'm using version 3.6 of fabric.js. Apparently there was a way to refresh the bounding box in older versions, but none of the code works with 3.6 - the functions no longer exist. I've tried several kludges to fix it, including ungrouping and regrouping the objects, without any luck so far.
Does anyone know how to force a recalculation of the bounding box in this situation? Thanks for any help.
In FabricJS 3 the best way to scale a group of objects is to change the scale value of the whole group:
group.set({ scaleX: value, scaleY: value })
I assume that sometimes it is required to scale only some objects inside a group or scale them with a different proportion. I think historically it was decided that this heavy operation needs to be triggered by the developer. There is a method for doing that addWithUpdate and it can be called without passing an object to it.
let canvas = new fabric.Canvas(document.querySelector('canvas'), {
backgroundColor: 'white'
}),
object,
lineCoords = [100, 75, 250, 75],
strokeWidth = parseFloat(document.querySelector('.strokewidth').value),
createArrow = () => {
return new fabric.Group(
[
new fabric.Line(lineCoords, {
left: lineCoords[0],
top: lineCoords[1],
stroke: 'red',
strokeWidth: strokeWidth,
strokeLineCap: "round",
}),
new fabric.Triangle({
width: strokeWidth + 22,
height: strokeWidth + 22,
fill: 'red',
left: lineCoords[0] + 150 + strokeWidth + 22 + ((strokeWidth + 22) / 2) - 14,
top: lineCoords[1] - (Math.floor((strokeWidth + 22) / 2)) + (Math.floor(strokeWidth / 2)),
angle: 90,
})
],
{ objectCaching: false }
)
}
let arrow = createArrow()
canvas.add(arrow)
canvas.setActiveObject(arrow)
canvas.renderAll()
$('body').on('input change', '.strokewidth', e => {
if (object = canvas.getActiveObject()) {
let value = parseFloat(e.target.value),
[ line, triangle ] = object.getObjects()
line.set({
strokeWidth: value
})
triangle.set({
width: value + 22,
height: value + 22,
left: line.left + line.width + value + 22 + ((value + 22) / 2) - 14,
top: line.top - (Math.floor((value + 22) / 2)) + (Math.floor(value / 2))
})
canvas.renderAll()
arrow.addWithUpdate()
}
})
body {
background: #f0f0f0;
padding: 0;
margin: 0;
}
canvas {
border: 1px solid lightgray;
}
div {
margin: 0.5em 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.0/fabric.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas width="400" height="150"></canvas>
<div>
<input type="range" class="strokewidth" min="1" max="40" value="5">
</div>

How to align Top/Left/Bottom/Right of selected objects (FabricJS)

var canvas = window._canvas = new fabric.Canvas('c');
var red = new fabric.Rect({
top: 100,
left: 0,
width: 80,
height: 50,
fill: 'red'
});
var blue = new fabric.Rect({
top: 0,
left: 100,
width: 50,
height: 70,
fill: 'blue'
});
var green = new fabric.Rect({
top: 100,
left: 100,
width: 60,
height: 60,
fill: 'green'
});
canvas.add(red, blue, green);
const alignLeft = document.getElementById('align_left');
alignLeft.onclick = function() {
const objs = canvas.getActiveObjects();
}
<script src="https://rawgithub.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" ></canvas>
<button id="align_left">Align Left</button>
Looking for a utility method to align edges (top,right,bottom,left) of array of objects from canvas.getActiveObjects in FabricJS v3
http://jsfiddle.net/rlightner/bzs0798x/1/
$('#align_left').click(function(){canvas.getActiveObject().set({left:0);})

Modify polyline

If I want to add an extra line to an existing polyline, should I remove this existing polyline from the canvas first, modify the points matrix, and add the new polyline? Or is it possible to change the existing polyline, like changing the text of a text object?
You may remove whole polyline and add a new one or else you need to calculate the dimensions(left,top and pathoffset) and set it to polyline.
DEMO
var canvas = new fabric.Canvas('c');
var points = [];
var random = fabric.util.getRandomInt;
points.push(new fabric.Point(random(100,200),random(200,300)));
points.push(new fabric.Point(random(200,300),random(100,200)));
points.push(new fabric.Point(random(200,250),random(150,200)));
var polyLine = new fabric.Polyline(points, {
stroke: 'black',
fill: ''
});
canvas.add(polyLine);
setPolyCoords();
function addPoint(){
polyLine.points.push(new fabric.Point(random(100,400),random(100,400)));
setPolyCoords();
}
function setPolyCoords(){
polyLine._calcDimensions();
polyLine.set({
top : polyLine.minY,
left : polyLine.minX,
pathOffset : {
x: polyLine.minX + polyLine.width / 2,
y: polyLine.minY + polyLine.height / 2
}
});
polyLine.dirty = true;
polyLine.setCoords();
canvas.renderAll();
}
canvas {
border: 1px solid #f00;
margin: 0px;
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.22/fabric.js"></script>
<button onclick='addPoint()'>Add Point</button>
<canvas id="c" width="400" height="400"></canvas>
With Fabric version 2.7.0 this is become easier then in #Durga his answer.
See the new code in the demo below.
You can also skip setting the dirty flag manually by passing objectCaching: false to your polyline during construction:
var polyLine = new fabric.Polyline(points, {
stroke: 'black',
fill: '',
objectCaching: false
});
DEMO
var canvas = new fabric.Canvas('c');
var points = [];
var random = fabric.util.getRandomInt;
points.push(new fabric.Point(random(100,200),random(200,300)));
points.push(new fabric.Point(random(200,300),random(100,200)));
points.push(new fabric.Point(random(200,250),random(150,200)));
var polyLine = new fabric.Polyline(points, {
stroke: 'black',
fill: ''
});
canvas.add(polyLine);
function addPoint(){
polyLine.points.push(new fabric.Point(random(100,400),random(100,400)));
polyLine.dirty = true;
canvas.renderAll();
}
canvas {
border: 1px solid #f00;
margin: 0px;
display: block;
}
<script src="https://rawgit.com/fabricjs/fabric.js/master/dist/fabric.min.js"></script>
<button onclick='addPoint()'>Add Point</button>
<canvas id="c" width="400" height="400"></canvas>

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>

How to use keyboards event while editing text in fabricjs

Is there a way to listen to key events while you are editing a text? (in itext or textbox or whatever). Our goal is to be able to hit cmd-b or cmd-i to set the selected part of your text in bold or italic.
Thanks for your help!
DEMO
window.addEventListener("keydown",onKeyDown);
function onKeyDown(e){
if (event.which == 73 && event.ctrlKey ) {
//ctrl+i
makeItalic();
}
if (event.which == 66 && event.ctrlKey ) {
//ctrl+b
makeBold();
}
}
var canvas = new fabric.Canvas('canvas');
var text = 'FabricJS Is Awsome';
var itext = new fabric.IText(text, {
left: 100,
top: 100,
fontSize: 40,
fill: '#000'
})
canvas.add(itext);
canvas.setActiveObject(itext);
function makeItalic(){
itext.setSelectionStyles({fontStyle:'italic'});// set your property
canvas.renderAll();
}
function makeBold(){
itext.setSelectionStyles({fontWeight:'bold'});
canvas.renderAll();
}
.canvas-wrappter {
position: relative;
}
canvas {
border: 1px solid #000;
}
.itext {
width: 300px;
background: transparent;
position: absolute;
z-index: 2;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.16/fabric.min.js"></script>
<div class="canvas-wrapper">
<canvas id="canvas" width="500" height="500"></canvas>
</div>
setSelectionStyles(style) use this function to set your style.

Resources