2012-06-30 42 views
44

Me gustaría poder guardar el estado del lienzo actual en una base de datos del servidor, probablemente como una cadena JSON, y luego restaurarlo con loadFromJSON . Por lo general, esto se logra fácilmente usando:Fabric.js - cómo guardar lienzo en el servidor con atributos personalizados

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 luego

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 fijando unos pocos atributos personalizados de los objetos de tela que estoy añadiendo a la lona mediante la orden interna Object#set método:

// 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 puedo comprobar la solicitud en el lado del servidor, veo que mis atributos personalizados no se analizan desde el lienzo y se envían junto con todo lo demas. 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 forma de abordar este problema, de modo que pueda guardar y restaurar el lienzo desde una cadena JSON enviada por el servidor, y el lienzo restaurado también incluirá mis atributos personalizados? Gracias.

Respuesta

59

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 clasificaríamos un fabric.Image 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 }); 
    } 
}); 

En primer lugar, le damos un tipo a estos objetos. Este tipo es utilizado por loadFromJSON para invocar automáticamente el método fabric.<type>.fromObject. En este caso, sería fabric.NamedImage.fromObject.

Luego sobrescribir initialize (constructor) método de instancia, también para establecer la propiedad "nombre" al inicializar un objeto (si se da esa propiedad).

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

Por último, también necesitaremos para aplicar esa fabric.NamedImage.fromObject que he mencionado anteriormente, de manera que loadFromJSON sabría qué método para 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)); 
    }); 
}; 

Nos estamos cargando una imagen aquí (de " object.src "), y luego crea una instancia de fabric.NamedImage. 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 para especificar que fabric.NamedImage es una "clase" asíncrono, meanining que su fromObject no devuelve una instancia, sino que lo pasa a una devolución de llamada:

fabric.NamedImage.async = true; 

Y ahora podemos tratar todo esto a cabo:

// 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); 
}); 
+0

Excelente respuesta, y funcionó como un encanto. ¡Gracias! – sa125

+1

En realidad, me aparece un error al intentar cargar el lienzo desde la cadena JSON. Estoy usando 'canvas.loadFromDatalessJSON (json)', que anteriormente funcionaba con 'fabric.Image'. Ahora que estoy usando 'fabric.NamedImage' como se sugirió anteriormente, obtengo' no puedo llamar al método 'setupState' of undefined'. ¿Cómo puedo arreglar esto? – sa125

+1

Parece ser un error en 'toDatalessJSON':/Tenemos cierta lógica de duplicación en toJSON y toDatalessJSON y quiero reescribir todo eso, haciéndolo agradable, limpio y consistente. Hará en algún momento en un futuro cercano. – kangax

2

Un enfoque más simple sería añadir 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); 
+0

Lo inverso debería funcionar sin problemas: https: // github.com/kangax/fabric.js/issues/471 – xmojmr

2

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

escribí una función que toma el lienzo de tela en el parámetro y devuelve una versión stringified 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 parece importar correctamente cuando utilizo canvas.loadFromJSON(), estoy usando tela 1.7.2.

Cuestiones relacionadas