Fabric.js: cómo guardar canvas en el servidor con atributos personalizados

Me gustaría poder guardar el estado del canvas actual en una base de datos del lado del servidor, probablemente como una cadena JSON, y luego restaurarlo con loadFromJSON . Típicamente, esto se logra fácilmente usando:

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

Y entonces

 function loadCanvas(json) { // parse the data into the canvas canvas.loadFromJSON(json); // re-render the canvas canvas.renderAll(); // optional canvas.calculateOffset(); } 

Sin embargo, también he estado configurando algunos atributos personalizados en los objetos de estructura que estoy agregando al canvas utilizando el método de Object#set incorporado:

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

El problema es que cuando consulto la solicitud en el lado del servidor, veo que mis atributos personalizados no se analizaron desde el canvas y se enviaron junto con todo lo demás. Esto probablemente tiene que ver con cómo el método toObject elimina cualquier cosa que no sea un atributo predeterminado en la clase de objeto. ¿Cuál sería una buena manera de abordar este problema, de forma que pueda guardar y restaurar el canvas desde una cadena JSON enviada por el servidor, y el canvas restaurado también incluirá mis atributos personalizados? Gracias.

Buena pregunta.

Si agrega propiedades personalizadas a los objetos, es probable que esos objetos sean “especiales” de alguna manera. Parece que subclasarlos sería una solución razonable.

Por ejemplo, así es como fabric.Image una fabric.Image . Imagen en una imagen con nombre. Esos objetos de imagen podrían tener nombres como “Gandalf” o “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 }); } }); 

Primero, damos un tipo a estos objetos. Este tipo es utilizado por loadFromJSON para invocar automáticamente fabric..fromObject method. En este caso, sería fabric.NamedImage.fromObject .

A continuación, sobrescribimos el método de instancia de initialize (constructor) para establecer también la propiedad “nombre” al inicializar un objeto (si se proporciona esa propiedad).

A continuación, sobrescribimos el método de instancia de toObject para incluir “nombre” en el objeto devuelto (esta es una piedra angular de la serialización de objetos en el tejido).

Finalmente, también necesitaremos implementar ese fabric.NamedImage.fromObject que mencioné anteriormente, para que loadFromJSON sepa qué método invocar durante el análisis JSON:

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

Estamos cargando una imagen aquí (desde “object.src”), y luego creando una instancia de fabric.NamedImage fuera de ella. Observe cómo en ese punto, el constructor ya se ocupará de la configuración de “nombre”, ya que sobrescribimos el método de “inicialización” más temprano.

Y también necesitaremos especificar que fabric.NamedImage es una “clase” asíncrona, lo que significa que fromObject no devuelve una instancia, sino que la pasa a una callback:

 fabric.NamedImage.async = true; 

Y ahora podemos intentarlo todo:

 // create image element var img = document.createElement('img'); img.src = 'http://sofes.miximages.com/database/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); }); 

Un enfoque más simple sería agregar las propiedades 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); 

Tuve el mismo problema pero no quería extender las clases de fabric.js.

Escribí una función que toma el canvas de tela en el parámetro y devuelve una versión codificada con mis atributos especiales:

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

Los atributos especiales parecen importados correctamente cuando uso canvas.loadFromJSON() , estoy usando fabric 1.7.2 .

Guau. ¿Me estoy perdiendo de algo?

Lo he hecho muchas veces y no necesita subclases sofisticadas.

Los documentos lo cubren: http://fabricjs.com/docs/fabric.Canvas.html#toJSON

Simplemente incluya una matriz de nombres de propiedades como cadenas en su llamada a toJSON ().

P.ej

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

Con suerte … para obtener puntos de bonificación, puede agregar una función reviver que rehidratará sus atributos personalizados.

De nuevo, esto está cubierto en los documentos y tiene un ejemplo.