Fabric.js - 将对象:modified事件同步到另一个客户端

4

协作模式:

如何最好地将客户端1的画布更改传播到客户端2的画布?以下是我如何捕获并发送事件到Socket.io。

$scope.canvas.on('object:modified',function(e) { 
  Socket.whiteboardMessage({
    eventId:'object:modified',
    event:e.target.toJSON()
  });
});

在接收端,这段代码非常适合在屏幕上添加新对象,但是我找不到关于如何选择和更新画布中现有对象的文档。
fabric.util.enlivenObjects([e.event], function(objects) {
  objects.forEach(function(o) {
    $scope.canvas.add(o);
  });
});

我看到对象有单独的设置器和一个批量设置器,但我无法根据事件数据选择现有对象。理想情况下,流程应该是:
  1. 接收具有目标对象数据的事件。
  2. 在画布中选择现有对象。
  3. 执行批量更新。
  4. 刷新画布。
希望有Fabric.JS经验的人能帮助我解决这个问题。谢谢! 更新的答案 - 感谢AJM! AJM正确地建议为每个新创建的元素使用唯一ID。我还能够为所有新创建的绘图路径创建一个新的ID。以下是它的工作原理:
var t = new fabric.IText('Edit me...', { 
  left: $scope.width/2-100, 
  top: $scope.height/2-50 
});
t.set('id',randomHash());
$scope.canvas.add(t);

我还捕获了新创建的路径并添加了一个id:
$scope.canvas.on('path:created',function(e) {
  if (e.target.id === undefined) {
    e.target.set('id',randomHash());
  }
});

然而,我遇到了一个问题,即我的ID在控制台日志中可见,但在执行object.toJSON()后不可见。这是因为Fabric有自己的序列化方法,将数据缩减为标准属性列表。要包含其他属性,我必须像这样对数据进行序列化以进行传输:

$scope.canvas.on('object:modified',function(e) { 
  Socket.whiteboardMessage({
    object:e.target.toJSON(['id']) // includes "id" in output.
  })
});

现在每个对象都有一个唯一的ID用于执行更新。在我的代码接收端,我添加了AJM的对象查找功能。我将这段代码放置在应用程序的“启动”部分,这样它只会运行一次(当然,在Fabric.js加载后!)

fabric.Canvas.prototype.getObjectById = function (id) {
    var objs = this.getObjects();
    for (var i = 0, len = objs.length; i < len; i++) {
        if (objs[i].id == id) {
            return objs[i];
        }
    }
    return 0;
};

现在,每当收到带有白板数据的新socket.io消息时,我可以通过以下代码在画布中找到它:

var obj = $scope.canvas.getObjectById(e.object.id);

插入和删除很容易,但对于更新来说,这段代码是解决问题的关键:

obj.set(e.object); // Updates properties
$scope.canvas.renderAll(); // Redraws canvas
$scope.canvas.calcOffset(); // Updates offsets

我需要处理以下事件,这都要求我将路径视为对象。

$scope.canvas.on('object:added',function(e) { });
$scope.canvas.on('object:modified',function(e) { });
$scope.canvas.on('object:moving',function(e) { });
$scope.canvas.on('object:removed',function(e) { });
$scope.canvas.on('path:created',function(e) { });
1个回答

2

我曾经做过类似的事情,涉及多个用户之间共享单个画布,并遇到了这个确切的问题。

为了解决这个问题,我使用了JavaScript UUID生成器为每个添加到画布上的对象添加了唯一ID(在我的情况下,可能有许多用户同时在画布上工作,因此我需要避免冲突;在您的情况下,更简单的方法可能会起作用)。

Fabric对象的set方法可以让您添加任意属性,例如id:o.set('id', yourid)。在将新的Fabric对象add()到画布上(并将其发送到网络)之前,附加一个ID属性。现在,您将拥有一个唯一的键,可以通过它来挑选出各个对象。

从那里开始,您需要一种按ID检索对象的方法。以下是我使用的方法:

fabric.Canvas.prototype.getObjectById = function (id) {
    var objs = this.getObjects();
    for (var i = 0, len = objs.length; i < len; i++) {
        if (objs[i].id == id) {
            return objs[i];
        }
    }

    return null;
};

当您从套接字接收数据时,通过ID从画布中获取该对象,并使用适当的set方法或整体复制属性进行改变(如果getObjectById返回null,则创建它)。


有趣,谢谢你的提示。你是如何解决在自由绘制模式下添加路径ID的问题的?听起来你使用了path:added并为其分配了一个ID,如果它还没有一个...这是正确的吗? - Noel Baron
1
很遗憾,我们的应用程序不需要支持自由绘图,所以我无法回答这个问题(我在我们的应用程序侧边栏上停靠了一个“调色板”,其中包含形状、箭头、图像和一些我创建的文本对象;除了自由绘图路径之外,我可以担保基本上所有东西)。猜测一下,但只要路径具有唯一的ID属性(存在或者你设置),一切都应该能够正常工作(你将能够通过ID选择它们,然后应用适当的变换)。 - ajm
1
绘图的工作方式与对象完全相同,只是我必须侦听path:created事件以设置ID。我使用完整代码更新了我的答案。 - Noel Baron
太好了!感谢您修改问题并添加内容;我相信其他人会发现它非常有用。 - ajm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接