17

Note: I have refereed SO question, but it is not useful for my case, because

1) I am trying to maintain previous border but as of now its recalculate border while scaling.

I have added below code to stop increasing border automatically while scaling the object. Now the issue is I have added a 5px border to object but when scaling the object then it is not maintaining the border which I added earlier.

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

Now what I want is to prevent increasing of border while scaling the object. Border should remain as it was earlier.

Here is snippet / Codepen

var canvas = new fabric.Canvas('canvas1');

$('.add_shape').click(function() {
  var cur_value = $(this).attr('data-rel');
  if (cur_value != '') {
    switch (cur_value) {
      case 'rectangle':
        var rect = new fabric.Rect({
          left: 50,
          top: 50,
          fill: '#aaa',
          width: 50,
          height: 50,
          opacity: 1,
          stroke: '#000',
          strokeWidth: 1
        });
        canvas.add(rect);
        canvas.setActiveObject(rect);
        break;
      case 'circle':
        var circle = new fabric.Circle({
          left: 50,
          top: 50,
          fill: '#aaa',
          radius: 50,
          opacity: 1,
          stroke: '#000',
          strokeWidth: 1
        });
        canvas.add(circle);
        canvas.setActiveObject(circle);
        break;
    }
  }
});

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

/* Control the border  */
$('#control_border').change(function() {
  var cur_value = parseInt($(this).val());
  var activeObj = canvas.getActiveObject();
  if (activeObj == undefined) {
    alert('Please select the Object');
    return false;
  }
  activeObj.set({
    strokeWidth: cur_value
  });
  canvas.renderAll();
});
button {
  max-resolution: 10px;
  height: 30px;
}

div {
  margin: 10px
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.0.0-beta.7/fabric.js"></script>

<div>
  <button class="add_shape" data-rel="circle">Add Circle</button>

  <button class="add_shape" data-rel="rectangle">Add Rectangle</button>

  <label class="control-label">Border</label>
  <input id="control_border" type="range" min="0" max="10" step="1" value="0" />
</div>

<canvas id="canvas1" width="600" height="300" style="border:1px solid #000000;"></canvas>

Steps

1) Add Rectangle
2) Apply the border (lets say 5)
3) Scale that object

Now you can see the applied border is gone. So how to resolve that?

Update

I have tried below option but its not working, basically i am trying to maintain strokeWidth/Border for objects like rectangle, circle, triangle, line, polygon

What i have tried so far:

//1st try
canvas.on('object:scaling', (e) => {
    var o = e.target;
    o.strokeWidth = o.strokeWidth / ((o.scaleX + o.scaleY) / 2);
    var activeObject = canvas.getActiveObject();
    activeObject.set('strokeWidth',o.strokeWidth);
});

//2nd try
canvas.on('object:scaling', (e) => {
    if (!o.strokeWidthUnscaled && o.strokeWidth) {
        o.strokeWidthUnscaled = o.strokeWidth;
    }
    if (o.strokeWidthUnscaled) {
        o.strokeWidth = o.strokeWidthUnscaled / o.scaleX;
    }
});

//3rd try
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();
};

Questions i have refereed:

https://github.com/kangax/fabric.js/issues/66

Unable to maintain thickness of strokeWidth while resizing in case of Groups in Fabricjs

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

Resize a fabricjs rect to maintain border size

https://github.com/kangax/fabric.js/issues/2012

But not able to found solution.

Durga
  • 15,263
  • 2
  • 28
  • 52
DS9
  • 2,995
  • 4
  • 52
  • 102
  • Possible duplicate of [Fabricjs How to scale object but keep the border (stroke) width fixed](https://stackoverflow.com/questions/39548747/fabricjs-how-to-scale-object-but-keep-the-border-stroke-width-fixed) – Brett DeWoody Feb 27 '18 at 09:39
  • Actually, i have used that code. But using that code, it recalculate the border and hence the issue occurred which i have added. – DS9 Feb 27 '18 at 09:40
  • @BrettDeWoody As you can see that i have used the accepted answer which you have tagged as duplicate. But using that answer issue occurred which i have added in question. So its not duplicate. – DS9 Feb 27 '18 at 09:43
  • 1
    `activeObj.set({strokeWidth:cur_value,strokeWidthUnscaled:null});` make strokeWidthUnscaled null of object, so while scalling it will reset – Durga Feb 27 '18 at 10:01
  • @Durga, is there a way to identify that what we have selected is group or not? i mean if we select single object then its not group, but if it has more than one element then its' group. – DS9 Feb 27 '18 at 10:13
  • which version of fabricjs are you using? – Durga Feb 27 '18 at 10:14
  • Fabric version: 2.0.0-beta.7 – DS9 Feb 27 '18 at 10:17
  • `canvas.getActiveObjects()` it will gives you all the selected object in an array, if length is 1 it's single or else group. – Durga Feb 27 '18 at 10:26
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/165901/discussion-between-ds9-and-durga). – DS9 Feb 27 '18 at 13:21
  • Hello, i ll try to show you how to do it with overriding standard methods. Will have some weird edge cases, but should be good – AndreaBogazzi Mar 04 '18 at 08:12
  • @AndreaBogazzi will wait for your answer. – DS9 Mar 07 '18 at 07:32
  • I have also this problem. stroke is reset to original stroke width. – Mayur Kukadiya Feb 07 '19 at 03:31

5 Answers5

15

This has become much easier as of Fabric.js version 2.7.0. There is now a strokeUniform property that when enabled, prevents the stroke width from being affected by the object's scale values.

obj.set('strokeUniform', true);

https://github.com/fabricjs/fabric.js/pull/5546

var canvas = new fabric.Canvas('canvas1');

$('.add_shape').click(function() {
  var cur_value = $(this).attr('data-rel');
  if (cur_value != '') {
    switch (cur_value) {
      case 'rectangle':
        var rect = new fabric.Rect({
          left: 50,
          top: 50,
          fill: '#aaa',
          width: 50,
          height: 50,
          opacity: 1,
          stroke: '#000',
          strokeWidth: 1,
          noScaleCache: false,
          strokeUniform: true,
        });
        canvas.add(rect);
        canvas.setActiveObject(rect);
        break;
      case 'circle':
        var circle = new fabric.Circle({
          left: 50,
          top: 50,
          fill: '#aaa',
          radius: 50,
          opacity: 1,
          stroke: '#000',
          strokeWidth: 1,
          noScaleCache: false,
          strokeUniform: true
        });
        canvas.add(circle);
        canvas.setActiveObject(circle);
        break;
    }
  }
});

/* Control the border  */
$('#control_border').change(function() {
  var cur_value = parseInt($(this).val());
  var activeObj = canvas.getActiveObject();
  if (activeObj == undefined) {
    alert('Please select the Object');
    return false;
  }
  activeObj.set({
    strokeWidth: cur_value
  });
  canvas.renderAll();
});
button {
  max-resolution: 10px;
  height: 30px;
}

div {
  margin: 10px
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.7.0/fabric.min.js"></script>

<div>
  <button class="add_shape" data-rel="circle">Add Circle</button>
  <button class="add_shape" data-rel="rectangle">Add Rectangle</button>
  <label class="control-label">Border</label>
  <input id="control_border" type="range" min="0" max="10" step="1" value="0" />
</div>

<canvas id="canvas1" width="600" height="300" style="border:1px solid #000000;"></canvas>
melchiar
  • 2,782
  • 16
  • 27
7

Set strokeWidth to strokeWidthUnscaled for first time then set @ at scalling, for caching make it false only when scalling after modified change it.

object.strokeWidth = object.strokeWidthUnscaled/((object.scaleX+object.scaleY)/2);

And for selecting new stroke width, do this

var newStrokeWidth = cur_value / ((activeObj.scaleX + activeObj.scaleY) / 2)
activeObj.set({
  strokeWidth: newStrokeWidth,
  strokeWidthUnscaled : cur_value
});

Example

var canvas = new fabric.Canvas('canvas1');
var globalStrokeWidth = 1;

$('.add_shape').click(function() {
  var cur_value = $(this).attr('data-rel');
  if (cur_value != '') {
    switch (cur_value) {
      case 'rectangle':
        var rect = new fabric.Rect({
          left: 50,
          top: 50,
          fill: '#aaa',
          width: 50,
          height: 50,
          opacity: 1,
          stroke: '#000',
          strokeWidth: globalStrokeWidth
        });
        canvas.add(rect);
        canvas.setActiveObject(rect);
        break;
      case 'circle':
        var circle = new fabric.Circle({
          left: 50,
          top: 50,
          fill: '#aaa',
          radius: 50,
          opacity: 1,
          stroke: '#000',
          strokeWidth: globalStrokeWidth
        });
        canvas.add(circle);
        canvas.setActiveObject(circle);
        break;
    }
  }
});

canvas.on('object:scaling', (e) => {
  var o = e.target;
  if (!o.strokeWidthUnscaled && o.strokeWidth) {
    o.strokeWidthUnscaled = o.strokeWidth;
  }
  if (o.strokeWidthUnscaled) {
    if(o.objectCaching) o.objectCaching = false;
    o.strokeWidth = o.strokeWidthUnscaled / ((o.scaleX + o.scaleY) / 2);
  }
});

canvas.on('object:modified', (e) => {
  var o = e.target;
  if(!o.objectCaching) o.objectCaching = true;
  canvas.renderAll();
});

/* Control the border  */
$('#control_border').change(function() {
  var cur_value = parseInt($(this).val());
  globalStrokeWidth = cur_value;
  var activeObj = canvas.getActiveObject();
  if (activeObj == undefined) {
    alert('Please select the Object');
    return false;
  }
  var newStrokeWidth = cur_value / ((activeObj.scaleX + activeObj.scaleY) / 2)
  activeObj.set({
    strokeWidth: newStrokeWidth,
    strokeWidthUnscaled : cur_value
  });
  activeObj.setCoords();
  canvas.renderAll();
});
button {
  max-resolution: 10px;
  height: 30px;
}

div {
  margin: 10px
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.0.0-beta.7/fabric.js"></script>

<div>
  <button class="add_shape" data-rel="circle">Add Circle</button>

  <button class="add_shape" data-rel="rectangle">Add Rectangle</button>

  <label class="control-label">Border</label>
  <input id="control_border" type="range" min="0" max="10" step="1" value="0" />
</div>

<canvas id="canvas1" width="600" height="300" style="border:1px solid #000000;"></canvas>
Durga
  • 15,263
  • 2
  • 28
  • 52
  • it works in all cases except this case: add the object and after that scale the object using the (top/bottom/left/right) marker (means scale using un proportionally (i.e. make make rectangle from square)). In that case border messed up. – DS9 Mar 05 '18 at 11:14
  • @DS9 ya I got, have to wait for Andrea's answer ;) – Durga Mar 07 '18 at 06:14
  • sure, will wait :). – DS9 Mar 07 '18 at 07:31
  • Why isn't this just built into text and itext? `strokeUniform` (in the docs) says that you can't use it with itext and text (and with specific methods / functions). – Shmack Jul 05 '23 at 23:21
2

While working on this issue, I've noticed that after resizing an object value of width and height doesn't change, it only changes scaleX and scaleY. To maintain the stroke width, you need to reset scaleX and scalyY to 1 and also update width and height with new values.

For rectangle:

var currObj = canvas.getActiveObject();
if (currObj.type === 'rect') {
  var newWidth = currObj.width * currObj.scaleX,
      newHeight = currObj.height * currObj.scaleY;

    currObj.set({
      'width': newWidth,
      'height': newHeight,
      scaleX: 1,
      scaleY: 1
    });
}

For Ellipse:

var currObj = canvas.getActiveObject();
if (currObj.type === 'ellipse') {
  var newRX = currObj.rx * currObj.scaleX,
      newRY = currObj.ry * currObj.scaleY;

  currObj.set({
    'rx': newRX,
    'ry': newRY,
    scaleX: 1,
    scaleY: 1
    });
}

Don't forget to call canvas.renderAll() later;

http://jsfiddle.net/eLoamgrq/5/

Rohit Verma
  • 299
  • 1
  • 8
1

In object:scaling, I commented out the calculation based on scale. Since the unscaled was = 1 and it was divided by scaling it always resulted in the border equaling a fraction (rather than staying put).

var canvas = new fabric.Canvas('canvas1');

$('.add_shape').click(function() {
  var cur_value = $(this).attr('data-rel');
  if (cur_value != '') {
    switch (cur_value) {
      case 'rectangle':
        var rect = new fabric.Rect({
          left: 50,
          top: 50,
          fill: '#aaa',
          width: 50,
          height: 50,
          opacity: 1,
          stroke: '#000',
          strokeWidth: 1
        });
        canvas.add(rect);
        canvas.setActiveObject(rect);
        break;
      case 'circle':
        var circle = new fabric.Circle({
          left: 50,
          top: 50,
          fill: '#aaa',
          radius: 50,
          opacity: 1,
          stroke: '#000',
          strokeWidth: 1
        });
        canvas.add(circle);
        canvas.setActiveObject(circle);
        break;
    }
  }
});

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

/* Control the border  */
$('#control_border').change(function() {
  var cur_value = parseInt($(this).val());
  var activeObj = canvas.getActiveObject();
  if (activeObj == undefined) {
    alert('Please select the Object');
    return false;
  }
  activeObj.set({
    strokeWidth: cur_value
  });
  canvas.renderAll();
});
button {
  max-resolution: 10px;
  height: 30px;
}

div {
  margin: 10px
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.0.0-beta.7/fabric.js"></script>

<div>
  <button class="add_shape" data-rel="circle">Add Circle</button>

  <button class="add_shape" data-rel="rectangle">Add Rectangle</button>

  <label class="control-label">Border</label>
  <input id="control_border" type="range" min="0" max="10" step="1" value="0" />
</div>

<canvas id="canvas1" width="600" height="300" style="border:1px solid #000000;"></canvas>
Ken Hansen
  • 131
  • 2
  • 1
    That's not an answer,as it doesn't maintain strokeWidth at all. – Durga Mar 02 '18 at 07:00
  • Check the developer tools. If you set the stroke width to 4 then resize, the stroke width stays at 4. It is not reset to a fraction of 1. Unless I misunderstood what was asked, this does maintain the set stroke width. – Ken Hansen Mar 02 '18 at 12:34
  • in your case while scaling check the strokeWidth of object, its changing. OP wants to maintain the same as before scaling. not just the value of strokeWidth. – Durga Mar 02 '18 at 12:43
0

If your object is inside a group, strokeUniform property does not work. In that case you can reduce the scaling of the inner object and update its width and height property on scaling. Example:

object.on('scaling', function() {
     
    object.item(0).set('scaleX', 1/object.scaleX);
    object.item(0).set('scaleY', 1/object.scaleY);
    var newobjwidth = Math.round(object.width * object.scaleX);
    var newobjheight = Math.round(object.height * object.scaleY);
    object.item(0).set('width', newobjwidth);
    object.item(0).set('height', newobjheight);
    object.item(0).setCoords();
 
}

If your object is blurry delete it and add it again after scaling.

Michal126
  • 1
  • 1