Fabric.js - 如何使用自定义属性在服务器上保存画布

Posted

技术标签:

【中文标题】Fabric.js - 如何使用自定义属性在服务器上保存画布【英文标题】:Fabric.js - how to save canvas on server with custom attributes 【发布时间】:2012-07-01 15:12:25 【问题描述】:

我希望能够将当前画布的状态保存到服务器端数据库,可能作为 JSON 字符串,然后使用loadFromJSON 恢复它。通常,这很容易使用:

var canvas = new fabric.Canvas();
function saveCanvas() 
    // convert canvas to a json string
    var json = JSON.stringify( canvas.toJSON() );

    // save via xhr
    $.post('/save',  json : json , function(resp) 
        // do whatever ...
    , 'json');

然后

function loadCanvas(json) 

  // parse the data into the canvas
  canvas.loadFromJSON(json);

  // re-render the canvas
  canvas.renderAll();

  // optional
  canvas.calculateOffset();

不过,我还使用内置的 Object#set 方法在要添加到画布中的织物对象上设置了一些自定义属性:

// get some item from the canvas
var item = canvas.item(0);

// add misc properties
item.set('wizard', 'gandalf');
item.set('hobbit', 'samwise');

// save current state
saveCanvas();

问题是,当我在服务器端检查请求时,我发现我的自定义属性没有从画布中解析出来并与其他所有内容一起发送。这可能与toObject 方法如何删除对象类中不是默认属性的任何内容有关。解决此问题的好方法是什么,这样我就可以从服务器发送的 JSON 字符串中保存 恢复画布,并且恢复的画布还将包括我的自定义属性?谢谢。

【问题讨论】:

【参考方案1】:

好问题。

如果您向对象添加自定义属性,那么这些对象可能在某种程度上是“特殊的”。似乎对它们进行子类化是一个合理的解决方案。

例如,下面是我们将fabric.Image 子类化为命名图像的方法。然后,这些图像对象可能具有“Gandalf”或“Samwise”之类的名称。

fabric.NamedImage = fabric.util.createClass(fabric.Image, 

  type: 'named-image',

  initialize: function(element, options) 
    this.callSuper('initialize', element, options);
    options && this.set('name', options.name);
  ,

  toObject: function() 
    return fabric.util.object.extend(this.callSuper('toObject'),  name: this.name );
  
);

首先,我们给这些对象一个类型。 loadFromJSON 使用此类型自动调用 fabric.<type>.fromObject 方法。在这种情况下,它将是fabric.NamedImage.fromObject

然后我们覆盖initialize(构造函数)实例方法,以便在初始化对象时也设置“name”属性(如果该属性已给定)。

然后我们覆盖toObject 实例方法以在返回的对象中包含“名称”(这是结构中对象序列化的基石)。

最后,我们还需要实现我之前提到的 fabric.NamedImage.fromObject,以便 loadFromJSON 知道在 JSON 解析期间调用哪个方法:

fabric.NamedImage.fromObject = function(object, callback) 
  fabric.util.loadImage(object.src, function(img) 
    callback && callback(new fabric.NamedImage(img, object));
  );
;

我们在这里加载一个图像(来自“object.src”),然后从中创建一个fabric.NamedImage 的实例。请注意,此时构造函数已经处理了“name”设置,因为我们之前覆盖了“initialize”方法。

我们还需要指定fabric.NamedImage 是一个异步“类”,这意味着它的fromObject 不返回实例,而是将其传递给回调:

fabric.NamedImage.async = true;

现在我们可以尝试一下:

// create image element
var img = document.createElement('img');
img.src = 'https://www.google.com/images/srpr/logo3w.png';

// create an instance of named image
var namedImg = new fabric.NamedImage(img,  name: 'foobar' );

// add it to canvas
canvas.add(namedImg);

// save json
var json = JSON.stringify(canvas);

// clear canvas
canvas.clear();

// and load everything from the same json
canvas.loadFromJSON(json, function() 

  // making sure to render canvas at the end
  canvas.renderAll();

  // and checking if object's "name" is preserved
  console.log(canvas.item(0).name);
);

【讨论】:

实际上,我在尝试从 JSON 字符串加载画布时遇到错误。我正在使用canvas.loadFromDatalessJSON(json),它以前使用过fabric.Image。现在我按照上面的建议使用fabric.NamedImage,我得到cannot call method 'setupState' of undefined。我该如何解决这个问题? 似乎是toDatalessJSON 中的一个错误:/ 我们在 toJSON 和 toDatalessJSON 中有一些重复的逻辑,我想重写所有这些,让它变得漂亮、干净和一致。在不久的将来会做。 不管当前的实现如何,似乎我自己真的把事情搞砸了 - 看到这个:***.com/a/11298971/187907 序列化章节 (fabricjs.com/fabric-intro-part-3/#serialization) 可以用这个来更新。现有文本有错误(例如,不在结构对象中存储子类,不使用连字符类型字符串)。 @kangax,这真的很酷,但我正在努力为fabric.Group 的子类做同样的事情(添加名称)它似乎可以保存JSON 但在加载时我得到Uncaught TypeError: Cannot read property 'async' of undefined 我对 JS 很陌生,所以可能把fromObject(); 弄错了。你会碰巧知道这个签名吗?【参考方案2】:

哇。我在这里遗漏了什么吗?

我已经做过很多次了,它不需要任何花哨的子类化。

文档涵盖了它:http://fabricjs.com/docs/fabric.Canvas.html#toJSON

只需在对 toJSON() 的调用中包含一组属性名称作为字符串。

例如

canvas.toJSON(['wizard','hobbit']);

希望....对于奖励积分,您可以添加一个 reviver 函数,它可以补充您的自定义属性。

这再次在文档中进行了介绍,并提供了一个示例。

【讨论】:

我现在意识到这个问题是 6 岁的大声笑。无论如何,我希望它可以帮助任何偶然发现它的人。 对我来说,canvas.toJSON 返回一个对象,而不是字符串。我还必须调用 JSON.stringify。【参考方案3】:

我有同样的问题,但我不想扩展 fabric.js 类。

我编写了一个函数,该函数将织物画布作为参数并返回带有我的特殊属性的字符串化版本:

function stringifyCanvas(canvas)

    //array of the attributes not saved by default that I want to save
    var additionalFields = ['selectable', 'uid', 'custom']; 

    sCanvas = JSON.stringify(canvas);
    oCanvas = JSON.parse(sCanvas) ;
    $.each(oCanvas.objects, function(n, object) 
        $.each(additionalFields, function(m, field) 
            oCanvas.objects[n][field] = canvas.item(n)[field];
        );
    );

    return JSON.stringify(oCanvas);     

当我使用canvas.loadFromJSON() 时,特殊属性似乎正确导入,我使用的是织物1.7.2

【讨论】:

【参考方案4】:

更简单的方法是在字符串化后添加属性:

var stringJson = JSON.stringify(this.canvas);
var objectJson = JSON.parse(string.Json);

//remove property1 property
delete objectJson.property1;

//add property2 property
delete objectJson.property2;

// stringify the object again
stringJson = JSON.stringify(objectJson);

// at this point stringJson is ready to be sent over to the server
$http.post('http://serverurl/',stringJson);

【讨论】:

逆向应该可以正常工作:github.com/kangax/fabric.js/issues/471【参考方案5】:

如果你不想每次调用canvas.toJSON()时都指定你使用的自定义属性,又不想使用复杂的子类化方式,这里有一个非常简单的扩展Fabric的toObject的方法方法。

//EXTEND THE PROPS FABRIC WILL EXPORT TO JSON
fabric.Object.prototype.toObject = (function(toObject) 
    return function() 
        return fabric.util.object.extend(toObject.call(this), 
            id: this.id,
            wizard: this.wizard,
            hobbit: this.hobbit,
        );
    ;
)(fabric.Object.prototype.toObject);

然后您可以在 Fabric 对象上设置自定义属性。

item.set("wizard","gandalf");
item.set("hobbit","bilbo");

当您调用canvas.toJSON() 时,这些属性将持续到输出。如果您随后在 JSON 输出中使用 canvas.loadFromJSON(),则自定义属性将被导入并应用于 Fabric 对象。

【讨论】:

以上是关于Fabric.js - 如何使用自定义属性在服务器上保存画布的主要内容,如果未能解决你的问题,请参考以下文章

Fabric.js - 自定义 Fabric.Image 类在“loadFromJSON”>“fromObject”中不起作用

如何在fabric js中覆盖canvas.add()方法

带有 Fabric.js 的 Node 中的 WebGL

fabric.js 组成员的属性不更新?

Fabric JS clipPath:裁剪后如何使图像适合画布?

旋转fabric.js时的组控制偏移