11

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.
small version

scaled version

Thank you very much!

trongd
  • 273
  • 2
  • 9

4 Answers4

12

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>
StefanHayden
  • 3,569
  • 1
  • 31
  • 38
  • Did you try with imported svg. I know it works with these builtin shape but not imported svg. – trongd Sep 18 '16 at 13:46
  • I don't know any good way with any arbitrary svg. Get your shape to be a single path. create a fabric.Path() and then add the stroke to that path object. – StefanHayden Sep 18 '16 at 14:12
  • 1
    Sorry, it was my mistake. Your solution should work! – trongd Sep 19 '16 at 02:18
12

There is a property called: strokeUniform Use it like this shape.set({stroke: '#f55b76', strokeWidth:2, strokeUniform: true })

Lonelydatum
  • 1,148
  • 1
  • 13
  • 26
9

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.

byoungb
  • 1,771
  • 20
  • 31
  • This does indeed look like a better solution, but I find the shape doesn't fill the bounding box. I tried to override _getTransformedDimensions as you suggested but the changes I make in my method don't seem to have any effect. See fiddle at http://jsfiddle.net/will_moore/a8wy0z4j/ – Will Moore Jun 08 '18 at 14:11
  • 1
    I think you may need to upgrade your fabricjs version.... I am running the new 2.X version now. But I also just tried this out with the latest 1.7.22 version here http://jsfiddle.net/63ybuxp0/ – byoungb Jun 08 '18 at 14:24
  • 1
    For people who, like me, didn't get this to work when scaling a grouped object. You can replace `ctx.scale(1 / this.scaleX, 1 / this.scaleY);` with `if (this.group) { ctx.scale(1 / this.group.scaleX, 1 / this.group.scaleY); } else { ctx.scale(1 / this.scaleX, 1 / this.scaleY); }` and it will also work for grouped scaling. – teuneboon May 02 '19 at 10:21
  • Hi @byoungb. I found this is the better solution. can you provide a solution for a grouped element? it is not applying for group object. – Yash Majithiya Jul 24 '19 at 06:20
  • Did you try @teuneboon's suggestion? – byoungb Jul 24 '19 at 16:33
2

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.

Dario
  • 39
  • 4