FabricJS iText grow from center - javascript

How can you make a text object on the FabricJS canvas grow/shrink from the center and expand bidirectionally when text is added or removed?
I have tried adding centeredScaling: true but this is only for resizing (scaling) the object, not adding/removing text.
If you check my demo you'll see that when you click the text and add more characters to the object it expands to the right only, leaving the text off center.
var canvas = this.__canvas = new fabric.Canvas('c');
var cWidth = 600;
var cHeight = 200;
canvas.setWidth(cWidth);
canvas.setHeight(cHeight);
function Addtext() {
canvas.add(new fabric.IText('Text', {
left: (cWidth / 2) - 83,
top: (cHeight / 2) - 25,
fontFamily: 'arial black',
fill: '#333',
fontSize: 50,
centeredScaling: true,
}));
}
#c {
border: 1px solid red;
top: 22px;
left: 0px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.min.js"></script>
<button onclick="Addtext()">Add Text</button>
<canvas id="c"></canvas>

Add align and origin of x and y to center.
var canvas = this.__canvas = new fabric.Canvas('c');
var cWidth = 600;
var cHeight = 200;
canvas.setWidth(cWidth);
canvas.setHeight(cHeight);
function Addtext() {
canvas.add(new fabric.IText('Text', {
left: (cWidth / 2) - 83,
top: (cHeight / 2) - 25,
fontFamily: 'arial black',
fill: '#333',
fontSize: 50,
align: 'mid', //added
originX: 'center', //added
originY: 'center', //added
centeredScaling: true,
}));
}
#c {
border: 1px solid red;
top: 22px;
left: 0px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.min.js"></script>
<button onclick="Addtext()">Add Text</button>
<canvas id="c"></canvas>

Related

How to select only the stroke in a transparent backgroud rectangle?

How can i make a transparent rectangle selectable only when i click on the stroke? I tried two different approach as you can see from the snippet below, the object on the left is a rectangle with a transparent background, the object on the right is a group object with multiple lines. I want to know how to be able to select the element only when i click on the stroke, with both objects if you click on the inner transparent area the object becomes selected.
var canvas = new fabric.Canvas('a');
canvas.add(new fabric.Rect({
left: 10,
top: 20,
height: 160,
width: 80,
fill: 'transparent',
stroke: 'red'
}));
var rectLine1 = new fabric.Line([0, 0, 80, 0], {
stroke: 'red',
left: 100,
top: 20
});
var rectLine2 = new fabric.Line([0, 160, 0, 0], {
stroke: 'red',
left: 100,
top: 20
});
var rectLine3 = new fabric.Line([0, 160, 0, 0], {
stroke: 'red',
left: 180,
top: 20
});
let rectLine4 = new fabric.Line([0, 0, 80, 0], {
stroke: 'red',
left: 100,
top: 180
});
var group = new fabric.Group([rectLine1, rectLine2, rectLine3, rectLine4], {
left: 110,
top: 20
})
canvas.add(group)
canvas.renderAll();
.c {
border: 2px solid black;
}
<script
src="https://code.jquery.com/jquery-2.2.4.min.js"
integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44="
crossorigin="anonymous"></script>
<script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.19/fabric.js"></script>
<canvas id="a" class="c" width="200" height="200"></canvas>
I found that setting perPixelTargetFind: true solves the problem, you can also set a tolerance on the canvas targetFindTolerance: 10 to be able to select objects with thin strokes. I'll paste the working code below in case anyone needs it.
var canvas = new fabric.Canvas('a', {
targetFindTolerance: 10
});
canvas.add(new fabric.Rect({
left: 10,
top: 20,
height: 160,
width: 80,
fill: 'transparent',
stroke: 'red',
perPixelTargetFind: true,
}));
canvas.renderAll();
.c {
border: 2px solid black;
}
<script
src="https://code.jquery.com/jquery-2.2.4.min.js"
integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44="
crossorigin="anonymous"></script>
<script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.19/fabric.js"></script>
<canvas id="a" class="c" width="200" height="200"></canvas>

selection border is incorrect while canvas's zoom value is not 1

Version 2.3.6
Steps to reproduce:
click 'select-all', selection is all right.
next click 'set zoom as 1.5' ,
then click 'select-all',
Result: Selection border is incorrect now.
$(document).ready(function() {
var canvas = new fabric.Canvas('canvas');
canvas.add(new fabric.Rect({
left: 50,
top: 50,
width: 75,
height: 50,
fill: 'green',
stroke: 'black',
strokeWidth: 3,
padding: 10
}));
canvas.add(new fabric.Circle({
left: 120,
top: 120,
radius: 30,
fill: 'gray',
stroke: 'black',
strokeWidth: 3
}));
$("#select-all").click(function() {
canvas.discardActiveObject();
var elements = canvas.getObjects();
var selection = new fabric.ActiveSelection(elements);
canvas.setActiveObject(selection);
canvas.requestRenderAll();
});
$("#set-zoom-15").click(function() {
canvas.setZoom(1.2);
});
$("#set-zoom-10").click(function() {
canvas.setZoom(1.0);
});
canvas.renderAll();
});
canvas {
border: 1px solid #ccc;
margin-top: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.6/fabric.min.js"></script>
<input id="select-all" type="button" value="select all" />
<input id="set-zoom-15" type="button" value="set zoom as 1.5" />
<input id="set-zoom-10" type="button" value="set zoom as 1" />
<canvas id="canvas" width="400" height="400" style="border:1px solid red" />
Expected Behavior:
Selection works fine ( like zoom is 1 ) when the zoom is 1.5.
Actual Behavior:
Selection is incorrect when the zoom is 1.5.
When you create your ActiveSelection, you need to pass in the canvas object.
var selection = new fabric.ActiveSelection(elements, {
canvas: canvas,
});
See http://fabricjs.com/manage-selection for documentation.
$(document).ready(function() {
var canvas = new fabric.Canvas('canvas');
canvas.add(new fabric.Rect({
left: 50,
top: 50,
width: 75,
height: 50,
fill: 'green',
stroke: 'black',
strokeWidth: 3,
padding: 10
}));
canvas.add(new fabric.Circle({
left: 120,
top: 120,
radius: 30,
fill: 'gray',
stroke: 'black',
strokeWidth: 3
}));
$("#select-all").click(function() {
canvas.discardActiveObject();
var elements = canvas.getObjects();
var selection = new fabric.ActiveSelection(elements, {
canvas: canvas,
});
canvas.setActiveObject(selection);
canvas.requestRenderAll();
});
$("#set-zoom-15").click(function() {
canvas.setZoom(1.2);
});
$("#set-zoom-10").click(function() {
canvas.setZoom(1.0);
});
canvas.renderAll();
});
canvas {
border: 1px solid #ccc;
margin-top: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.6/fabric.min.js"></script>
<input id="select-all" type="button" value="select all" />
<input id="set-zoom-15" type="button" value="set zoom as 1.5" />
<input id="set-zoom-10" type="button" value="set zoom as 1" />
<canvas id="canvas" width="400" height="400" style="border:1px solid red" />

FabricJS add looped images to group programmatically

I have a set of letters. A word is built form these letters. I loop through the data ids to build the word.
However I cannot seem to group them together.
If I manually select the letters and try
canvas.getActiveObject().toGroup();
canvas.requestRenderAll();
It will group them together.
But I need to do this programatically and when I add
canvas.add(img).setActiveObject();
it will only select the last object added to the canvas.
Here is my function to build the word
function buildImage(i) {
var wordo = jQuery('*[data-id="' + wordArray[i] + '"]').attr('src');
fabric.Image.fromURL(wordo, function(img, letterWidth) {
// fabric.Image.fromURL(tiles[i], function (img) {
img.scale(scaleO).set({
left: offset[i],
top: wordPositionY,
selectable: true,
cornerStyle: 'circle',
cornerColor: '#f6ff00',
cornerStrokeColor: '#f6ff00',
transparentCorners: false,
borderColor: '#f9f60b',
cornerSize: 22,
hasRotatingPoint: true,
id: 'letter'
});
img.setControlsVisibility({
mt: false,
mr: false,
mb: false,
ml: false
});
canvas.add(img);
});
}
and here is the loop that calls it
for (var i = 0; i < letters; i++) {
buildImage(i);
}
I want to add these letters to the canvas as a group which can be un grouped if needed as well. Is there a way to do this?
Here is my JSfiddle
As fabric.Image.fromURL is asynchronous, so you have to wait till all the images loaded, after loading all images create a group of all images.
$(document).ready(function() {
canvasWidth = 448;
canvasHeight = 390;
canvas = new fabric.Canvas('c', {
width: canvasWidth,
height: canvasHeight,
containerClassName: 'c',
selection: true,
preserveObjectStacking: true,
});
var word = 'abc';
let letters = word.length;
let imageLoaded = 0;
var letterWidth = canvas.width / letters;
var numArr = [];
var wordArray = word.split("");
var offset = [
0,
letterWidth,
letterWidth * 2,
letterWidth * 3,
letterWidth * 4,
letterWidth * 5,
letterWidth * 6,
letterWidth * 7,
letterWidth * 8,
letterWidth * 9,
letterWidth * 10,
letterWidth * 11,
letterWidth * 12,
letterWidth * 13,
];
for (var i = 0; i < letters; i++) {
buildImage(i);
}
let imageData = [];
var scaleO = letterWidth / 448;
var wordPositionY = (canvasHeight - letterWidth) / 2
// creates a new scope for the passed in value of i when called:
function buildImage(i) {
var wordo = jQuery('*[data-id="' + wordArray[i] + '"]').attr('src');
fabric.Image.fromURL(wordo, function(img, letterWidth) {
// fabric.Image.fromURL(tiles[i], function (img) {
img.scale(scaleO).set({
left: offset[i],
top: wordPositionY,
id: 'letter'
});
img.setControlsVisibility({
mt: false,
mr: false,
mb: false,
ml: false
});
imageData.push(img);
if (letters == ++imageLoaded) {
setGroup();
}
});
}
function setGroup() {
let group = new fabric.Group(imageData, {
top: 100,
selectable: true,
cornerStyle: 'circle',
cornerColor: '#f6ff00',
cornerStrokeColor: '#f6ff00',
transparentCorners: false,
borderColor: '#f9f60b',
cornerSize: 22,
})
canvas.add(group)
imageData = [];
}
});
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#banner-message {
background: #fff;
border-radius: 4px;
padding: 20px;
font-size: 25px;
text-align: center;
transition: all 0.2s;
margin: 0 auto;
width: 450px;
}
button {
background: #0084ff;
border: none;
border-radius: 5px;
padding: 8px 14px;
font-size: 15px;
color: #fff;
}
#banner-message.alt {
background: #0084ff;
color: #fff;
margin-top: 40px;
}
#banner-message.alt button {
background: #fff;
color: #000;
}
#c {
background: #e6e6e6;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="banner-message">
<p>My Canvas</p>
<canvas id="c"></canvas>
</div>
<div class="letters">
<img src="https://i.imgur.com/7lXiKkD.png" height="32" id="theme_img" data-id="a" alt="">
<img src="https://i.imgur.com/gMqYdMB.png" height="32" id="theme_img" data-id="b" alt="">
<img src="https://i.imgur.com/1CMBCqv.png" height="32" id="theme_img" data-id="c" alt="">
</div>

How to style part of my drawn fabricjs text object?

I have some code that allows me to toggle between text objects. I'd like to have the text objects include some styling, like Bold, Italic, etc for titles and such. text, however, am not sure how to accomplish this. Here's my current code:
var canvas = this.__canvas = new fabric.Canvas('c');
canvas.setHeight(300);
canvas.setWidth(500);
function textOne() {
canvas.clear();
canvas.add(new fabric.IText('Regular Font', {
left: 50,
top: 100,
fontFamily: 'arial',
fill: '#333',
fontSize: 50
}));
}
function textTwo() {
canvas.clear();
canvas.add(new fabric.IText('Bold Font', {
left: 50,
top: 100,
fontFamily: 'arial',
fontWeight: 'bold',
fill: '#333',
fontSize: 50
}));
}
canvas {
border: 1px solid #dddddd;
border-radius: 3px;
margin-top: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.20/fabric.min.js"></script>
<button onclick="textOne()">One</button>
<button onclick="textTwo()">Two</button>
<canvas id="c"></canvas>
You can use the styles property to style individual characters
http://fabricjs.com/docs/fabric.IText.html#styles
var canvas = this.__canvas = new fabric.Canvas('c');
canvas.setHeight(300);
canvas.setWidth(500);
function textOne() {
canvas.clear();
canvas.add(new fabric.IText('Regular Font', {
left: 50,
top: 100,
fontFamily: 'arial',
fill: '#333',
fontSize: 50,
styles:{
0:{
0:{fontWeight: 'bold'},
1:{fontWeight: 'bold'}
}
}
}));
}
function textTwo() {
canvas.clear();
canvas.add(new fabric.IText('Bold Font', {
left: 50,
top: 100,
fontFamily: 'arial',
fontWeight: 'bold',
fill: '#333',
fontSize: 50,
styles:{
0:{
0:{fontSize: 40},
1:{fontSize: 30}
}
}
}));
}
canvas {
border: 1px solid #dddddd;
border-radius: 3px;
margin-top: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.20/fabric.min.js"></script>
<button onclick="textOne()">One</button>
<button onclick="textTwo()">Two</button>
<canvas id="c"></canvas>
If you want to also update the styles dynamically, you can use setSelectionStyles method like this:
const text = new fabric.TextBox('Regular Font', {
left: 50,
top: 100,
fontFamily: 'arial',
fill: '#333',
fontSize: 50,
});
const start = 0
const end = 5
text.setSelectionStyles({fontWeight: 'bold'}, start, end);

How to display element in proper position in FabricJs despite on it originX and Y?

I have two almost identical elements except for their top and origin.
I don't understand how to place them on the same left with 2 different origins.
Here is the expected view:
And here is what I get:
--> Here is the link to jsFiddler <--
Note: Their LEFT is same! This is what I'm getting from backend.
Tnx
var canvas = new fabric.Canvas('paper');
const red = new fabric.Rect({
left: 100,
top: 50,
fill: 'red',
width: 200,
height: 100,
originX: 'center',
originY: 'center'
});
const yellow = new fabric.Rect({
left: 100,
top: 150,
fill: 'yellow',
width: 200,
height: 100,
originX: 'left',
originY: 'top'
});
canvas.add(red);
canvas.add(yellow);
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.0/fabric.min.js"></script>
<canvas id="paper" width="400" height="400" style="border:1px solid #ccc"></canvas>
Is this what you want? Here left and top attrs are same.
var canvas = new fabric.Canvas('paper');
const red = new fabric.Rect({
left: 100,
top: 150,
fill: 'red',
width: 200,
height: 100,
originX: 'left',
originY: 'bottom'
});
const yellow = new fabric.Rect({
left: 100,
top: 150,
fill: 'yellow',
width: 200,
height: 100,
originX: 'left',
originY: 'top'
});
canvas.add(red);
canvas.add(yellow);
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.0/fabric.min.js"></script>
<canvas id="paper" width="400" height="400" style="border:1px solid #ccc"></canvas>

Categories