web-dev-qa-db-fra.com

Fabric.js - comment enregistrer le canevas sur le serveur avec des attributs personnalisés

J'aimerais pouvoir enregistrer l'état actuel du canevas dans une base de données côté serveur, probablement sous forme de chaîne JSON, puis le restaurer plus tard avec loadFromJSON. En règle générale, cela se fait facilement en utilisant:

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');
}

Puis

function loadCanvas(json) {

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

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

  // optional
  canvas.calculateOffset();
}

Cependant, j'ai également défini quelques attributs personnalisés sur les objets de tissu que j'ajoute au canevas à l'aide du _ Object#set méthode:

// 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();

Le problème est que lorsque je vérifie la demande côté serveur, je constate que mes attributs personnalisés n'ont pas été analysés à partir du canevas et envoyés avec tout le reste. Cela a probablement à voir avec la façon dont la méthode toObject supprime tout ce qui n'est pas un attribut par défaut dans la classe d'objets. Quelle serait une bonne façon de résoudre ce problème, de telle sorte que je pourrai à la fois enregistrer et restaurer le canevas à partir d'une chaîne JSON envoyée par le serveur, et le canevas restauré comprendra également mon personnalisé les attributs? Merci.

51
sa125

Bonne question.

Si vous ajoutez des propriétés personnalisées à des objets, ces objets sont probablement "spéciaux" d'une manière ou d'une autre. Il semble que les sous-classer serait une solution raisonnable.

Par exemple, voici comment nous sous-classerions un fabric.Image dans une image nommée. Ces objets image pourraient alors avoir des noms comme "Gandalf" ou "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 });
  }
});

Tout d'abord, nous donnons à ces objets un type. Ce type est utilisé par loadFromJSON pour appeler automatiquement fabric.<type>.fromObject méthode. Dans ce cas, ce serait fabric.NamedImage.fromObject.

Ensuite, nous remplaçons la méthode d'instance initialize (constructeur), pour définir également la propriété "name" lors de l'initialisation d'un objet (si cette propriété est donnée).

Ensuite, nous remplaçons la méthode d'instance toObject pour inclure "nom" dans l'objet renvoyé (c'est la pierre angulaire de la sérialisation des objets dans le tissu).

Enfin, nous devons également implémenter cette fabric.NamedImage.fromObject que j'ai mentionné précédemment, afin que loadFromJSON sache quelle méthode invoquer pendant l'analyse JSON:

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

Nous chargeons une image ici (à partir de "object.src"), puis créons une instance de fabric.NamedImage en dehors de ça. Notez qu'à ce stade, le constructeur se chargera déjà du paramètre "nom", car nous avons remplacé la méthode "initialiser" plus tôt.

Et nous devrons également spécifier que fabric.NamedImage est une "classe" asynchrone, ce qui signifie que son fromObject ne renvoie pas d'instance, mais la transmet à un rappel:

fabric.NamedImage.async = true;

Et maintenant, nous pouvons tout essayer:

// 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);
});
68
kangax

Sensationnel. Est-ce que j'ai râté quelque chose?

Je l'ai fait beaucoup de fois et il n'a pas besoin de sous-classement sophistiqué.

Les documents le couvrent: http://fabricjs.com/docs/fabric.Canvas.html#toJSON

Incluez simplement un tableau de noms de propriétés sous forme de chaînes dans votre appel à toJSON ().

Par exemple

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

Avec un peu de chance .... pour les points bonus, vous pouvez ajouter une fonction reviver qui réhydratera vos attributs personnalisés.

Encore une fois, cela est couvert dans les documents et a un exemple.

7
kbcool

J'ai eu le même problème mais je ne voulais pas étendre les classes fabric.js.

J'ai écrit une fonction qui prend le canevas de tissu en paramètre et retourne une version stringifiée avec mes attributs spéciaux:

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);     
}

Les attributs spéciaux semblent correctement importés lorsque j'utilise canvas.loadFromJSON(), j'utilise fabric 1.7.2.

3
adrien54

Une approche plus simple serait d'ajouter les propriétés post-stringify:

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);
2
Dan Ochiana

Si vous ne souhaitez pas spécifier les attributs personnalisés que vous utilisez chaque fois que vous appelez canvas.toJSON(), et que vous ne souhaitez pas utiliser une approche de sous-classement compliquée, voici une manière très simple d'étendre 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);

Vous pouvez ensuite définir des propriétés personnalisées sur les objets Fabric.

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

Et lorsque vous appelez canvas.toJSON(), ces propriétés persistent dans la sortie. Si vous utilisez ensuite canvas.loadFromJSON() avec votre sortie JSON, les attributs personnalisés seront importés et appliqués aux objets Fabric.

0
Weft