在不调整文本框大小的情况下调整 Fabric Rect 的大小
Resize Fabric Rect without resizing Textbox
在这个 jsFiddle 中,我有一个 Fabric 组,其中包含一个 Rect 和一个 Textbox。我需要能够在不缩放文本的情况下缩放 Rect,所以我试图在选择组时取消组合,并在清除选择时再次组合。还有:
- Rect 和 Textbox 被分组以便能够将它们一起移动。
- 文本需要可编辑。
- 文本需要位于矩形的顶部(即应始终可见)。
如何使 jsFiddle 工作?
注意
文本和矩形总是一起移动,甚至在任何选择之前或之后。
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 ], {});
});
编辑:OP 阐明了一些要求,因此相应地编辑了解决方案。
虽然起初分组似乎是个好主意,但如果您仔细想想,您的 text/rect 组合需要的唯一分组功能就是一起移动的能力。这意味着创建一个组并尝试禁用所有不需要的功能实际上比将 Text 粘贴到您的 Rect 并只处理您关心的事件更难。
Fabric.js 有一个很棒的 subclassing 机制,我们将使用它来扩展 fabric.Rect
class.
下面的代码几乎是不言自明的,我只注意几个关键方面:
rectOptions
和 textOptions
传递给 fabric.RectWithText
构造函数,是您通常传递给 fabric.Rect
和 fabric.Textbox
构造函数的对象。
Textbox
被 RectWithText
实例的 text
属性 引用。
recalcTextPosition
使用几个三角公式计算文本相对于矩形的位置,给定两者之间的初始偏移量。
- 我们需要密切关注矩形的
moving
、scaling
和 rotating
事件,以便顺利地重新计算文本的位置。
mousedown:before
、mousedblclick
、editing:exited
确保文本在双击时保持可编辑状态。
- 我们通过在单击对象时修改
canvas.preserveObjectStacking
将文本保持在顶部。
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>
在这个 jsFiddle 中,我有一个 Fabric 组,其中包含一个 Rect 和一个 Textbox。我需要能够在不缩放文本的情况下缩放 Rect,所以我试图在选择组时取消组合,并在清除选择时再次组合。还有:
- Rect 和 Textbox 被分组以便能够将它们一起移动。
- 文本需要可编辑。
- 文本需要位于矩形的顶部(即应始终可见)。
如何使 jsFiddle 工作?
注意
文本和矩形总是一起移动,甚至在任何选择之前或之后。
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 ], {});
});
编辑:OP 阐明了一些要求,因此相应地编辑了解决方案。
虽然起初分组似乎是个好主意,但如果您仔细想想,您的 text/rect 组合需要的唯一分组功能就是一起移动的能力。这意味着创建一个组并尝试禁用所有不需要的功能实际上比将 Text 粘贴到您的 Rect 并只处理您关心的事件更难。
Fabric.js 有一个很棒的 subclassing 机制,我们将使用它来扩展 fabric.Rect
class.
下面的代码几乎是不言自明的,我只注意几个关键方面:
rectOptions
和textOptions
传递给fabric.RectWithText
构造函数,是您通常传递给fabric.Rect
和fabric.Textbox
构造函数的对象。Textbox
被RectWithText
实例的text
属性 引用。recalcTextPosition
使用几个三角公式计算文本相对于矩形的位置,给定两者之间的初始偏移量。- 我们需要密切关注矩形的
moving
、scaling
和rotating
事件,以便顺利地重新计算文本的位置。 mousedown:before
、mousedblclick
、editing:exited
确保文本在双击时保持可编辑状态。- 我们通过在单击对象时修改
canvas.preserveObjectStacking
将文本保持在顶部。
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>