In this jsFiddle I have a Fabric group containing a Rect and a Textbox. I need to be able to scale the Rect without scaling the text, so I'm trying to ungroup when the group is selected, and group again when the selection is cleared. Also:
How to make the jsFiddle work?
NOTE
The text and the rectangle always move together, even before or after any selection.
var canvas = window._canvas = new fabric.Canvas('c');
var text = new fabric.Textbox("Some text", {
width: 100,
height: 22,
fontSize: 12,
editable: true
});
var rect = new fabric.Rect({
width: 100,
height: 22,
fill: 'yellow'
});
var group = new fabric.Group([ rect, text ], {
left: 30,
top: 30
});
canvas.add(group);
group.on('selected', function (e){
canvas.remove(group);
canvas.add(rect);
canvas.add(text);
canvas.renderAll();
canvas.setActiveObject(rect);
});
canvas.on('selection:cleared', function(e) {
group = new fabric.Group([ rect, text ], {});
});
EDIT: the OP clarified some of the requirements, so the solution was edited accordingly.
While grouping may seem like a good idea at first, if you think about it, the only group feature your text/rect combination needs is the ability to move together. Which means that creating a group and trying to disable all the unwanted features is actually harder than glueing Text to your Rect and only handling events that you care about.
Fabric.js has a wonderful subclassing mechanism, which we'll use to extend fabric.Rect
class.
The code below is pretty much self-explanatory, I'll just note several key aspects:
rectOptions
and textOptions
passed into the fabric.RectWithText
constructor, are the objects you would normally pass into fabric.Rect
and fabric.Textbox
constructors, accordingly.Textbox
is referenced by a text
property of the RectWithText
instance.recalcTextPosition
uses a couple of trigonometry formulas to calculate text's position in relation to the rectangle, given the initial offset between the two.moving
, scaling
, and rotating
events to recalculate text's position smoothly.mousedown:before
, mousedblclick
, editing:exited
make sure the text stays editable on double click.canvas.preserveObjectStacking
when the object is clicked on.const canvas = new fabric.Canvas('c')
fabric.RectWithText = fabric.util.createClass(fabric.Rect, {
type: 'rectWithText',
text: null,
textOffsetLeft: 0,
textOffsetTop: 0,
_prevObjectStacking: null,
_prevAngle: 0,
recalcTextPosition: function () {
const sin = Math.sin(fabric.util.degreesToRadians(this.angle))
const cos = Math.cos(fabric.util.degreesToRadians(this.angle))
const newTop = sin * this.textOffsetLeft + cos * this.textOffsetTop
const newLeft = cos * this.textOffsetLeft - sin * this.textOffsetTop
const rectLeftTop = this.getPointByOrigin('left', 'top')
this.text.set('left', rectLeftTop.x + newLeft)
this.text.set('top', rectLeftTop.y + newTop)
},
initialize: function (rectOptions, textOptions, text) {
this.callSuper('initialize', rectOptions)
this.text = new fabric.Textbox(text, {
...textOptions,
selectable: false,
evented: false,
})
this.textOffsetLeft = this.text.left - this.left
this.textOffsetTop = this.text.top - this.top
this.on('moving', () => {
this.recalcTextPosition()
})
this.on('rotating', () => {
this.text.rotate(this.text.angle + this.angle - this._prevAngle)
this.recalcTextPosition()
this._prevAngle = this.angle
})
this.on('scaling', (e) => {
this.recalcTextPosition()
})
this.on('added', () => {
this.canvas.add(this.text)
})
this.on('removed', () => {
this.canvas.remove(this.text)
})
this.on('mousedown:before', () => {
this._prevObjectStacking = this.canvas.preserveObjectStacking
this.canvas.preserveObjectStacking = true
})
this.on('mousedblclick', () => {
this.text.selectable = true
this.text.evented = true
this.canvas.setActiveObject(this.text)
this.text.enterEditing()
this.selectable = false
})
this.on('deselected', () => {
this.canvas.preserveObjectStacking = this._prevObjectStacking
})
this.text.on('editing:exited', () => {
this.text.selectable = false
this.text.evented = false
this.selectable = true
})
}
})
const rectOptions = {
left: 10,
top: 10,
width: 200,
height: 75,
fill: 'rgba(30, 30, 30, 0.3)',
}
const textOptions = {
left: 35,
top: 30,
width: 150,
fill: 'white',
shadow: new fabric.Shadow({
color: 'rgba(34, 34, 100, 0.4)',
blur: 2,
offsetX: -2,
offsetY: 2
}),
fontSize: 30,
}
const rectWithText = new fabric.RectWithText(rectOptions, textOptions, 'Some text')
canvas.add(rectWithText)
body {
background: ivory;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.4.0/fabric.js"></script>
<canvas id="c" width="300" height="200"></canvas>