Canvas/FabricJS 中的对话泡泡?

Speech bubbles in Canvas/FabricJS?

正在寻找一种在我网站的 FabricJS 中制作常规对话泡泡的方法 canvas。在你标记这个 post 之前,我 做了 看到了 this question,它没有正确的答案并且是为 WordPress 设计的所以它对我没有特别的用处.

我想要的很清楚:一个带有文本的对话泡泡和一个 tail/handle,您可以拖动它以将其指向某物。

我找到了 this library 但我似乎无法在我的 FabricJS 中显示它 canvas?如果您能向我解释如何将此库添加到我的 canvas 中或提供另一种制作对话泡泡的方法,那就太棒了。

我对 Fabric.js 进行了一些研究,并设法创建了一个程序对话泡泡,但我无法快速将其转换为 Fabric.js class(这会使如果你想在你的 canvas 上有多个对话泡泡,请注意)。也许它对您或其他人仍然有帮助 https://codepen.io/timohausmann/pen/poywXzg

它主要是创建一个文本框,并根据文本的边界框更新它周围的矩形的位置。

var bound = textbox.getBoundingRect();
rect.left = bound.left - boxPadding;
rect.top = bound.top - boxPadding;
rect.width = bound.width + (boxPadding*2);
rect.height = bound.height + (boxPadding*2);

对于尾巴,我只是创建了一个透明的 Rect,您可以拖动它并使用它的坐标在“句柄”和文本框中心之间绘制一个具有三个点的多边形 [A]。为了确保无论位置如何,尾巴都保持一定的宽度,我计算了句柄和对话气泡中心之间的度数 [B]。为了保持文本框和句柄的位置同步,我计算了文本框移动了多少,然后简单地将差异添加到句柄位置 [C].

//calculate degree between textbox and handle [B]
var angleRadians = Math.atan2(handle.top - textbox.top, 
                              handle.left - textbox.left);
var offsetX = Math.cos(angleRadians + (Math.PI/2));
var offsetY = Math.sin(angleRadians + (Math.PI/2));

//update the polygon [A]
poly.points[0].x = handle.left;
poly.points[0].y = handle.top;
poly.points[1].x = textbox.left - (offsetX * arrowWidth);
poly.points[1].y = textbox.top - (offsetY * arrowWidth); 
poly.points[2].x = textbox.left + (offsetX * arrowWidth);
poly.points[2].y = textbox.top + (offsetY * arrowWidth);

//update the handle when the textbox moved [C]
if(textbox.left !== textbox.lastLeft || 
   textbox.top !== textbox.lastTop) {
  handle.left += (textbox.left - textbox.lastLeft);
  handle.top += (textbox.top - textbox.lastTop);
  handle.setCoords();
}

免责声明:我不是 Fabric.js 专家,也许图书馆有一些捷径。

@Til Hausmann 的回答很好(谢谢!)。 但是,当我尝试存储和加载 canvas 数据(分别通过 canvas.toJSONcanvas.loadFromJSON 时,我 运行 遇到了一些问题。

经过一些摆弄,这可以通过

解决
  • updateBubble()方法中为两个多边形存储lastLeftlastTop
poly.lastLeft = Math.min(handle.left, textBox.left);
poly.lastTop = Math.min(handle.top, textBox.top);
  • 在加载数据后设置多边形的 left / top 属性:
canvas.loadFromJSON(jsonData, () => {
   const poly = // ...
   const poly2 = // ...
    
   poly.left = poly.lastLeft;
   poly.top = poly.lastTop;

    poly2.left = poly2.lastLeft;
    poly2.top = poly2.lastTop;

    // ...

    // Important:
    canvas.renderAll();
});
  • 全套形状属性传递给canvas.toJSON()
canvas.toJSON(
   ['lastLeft', 'lastTop'].concat(
    Object.keys(handleProperties),
    Object.keys(polyProperties),
    Object.keys(poly2Properties),
    Object.keys(textRectProperties)
))

令我惊讶的是第 (3) 步实际上是必要的,但没有它就无法工作...