Fabric.js – hoe canvas op server op te slaan met aangepaste attributen

Ik wil de huidige canvasstatus kunnen opslaan in een server-side database, waarschijnlijk als een JSON-string, en deze later herstellen met loadFromJSON. Meestal is dit eenvoudig te bereiken met:

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

En dan

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

Ik heb echter ook een paar aangepaste attributen ingesteld op de stofobjecten die ik aan het canvas toevoeg met behulp van de ingebouwde Object#set-methode:

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

Het probleem is dat wanneer ik het verzoek aan de serverzijde controleer, ik zie dat mijn aangepaste kenmerken niet van het canvas zijn geparseerd en samen met al het andere zijn verzonden. Dit heeft waarschijnlijk te maken met hoe de methode toObjectalles verwijdert dat geen standaardkenmerk is in de objectklasse. Wat zou een goede manier zijn om dit probleem aan te pakken, zodat ik het canvas zowel enkan opslaan vanuit een JSON-string die door de server is verzonden, en het herstelde canvas ook mijn aangepaste attributen? bedankt.


Antwoord 1, autoriteit 100%

Goede vraag.

Als u aangepaste eigenschappen aan objecten toevoegt, zijn die objecten op de een of andere manier waarschijnlijk ‘speciaal’. Het lijkt erop dat het een redelijke oplossing zou zijn om ze in subclassificaties te plaatsen.

Hier ziet u bijvoorbeeld hoe we een fabric.Imagesubclasseren in een benoemde afbeelding. Die afbeeldingsobjecten kunnen dan namen hebben als “Gandalf” of “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 });
  }
});

Eerst geven we deze objecten een type. Dit type wordt gebruikt door loadFromJSONom automatisch de methode fabric.<type>.fromObjectaan te roepen. In dit geval zou het fabric.NamedImage.fromObjectzijn.

Vervolgens overschrijven we initialize(constructor) instantiemethode, om ook de eigenschap “name” in te stellen bij het initialiseren van een object (als die eigenschap wordt gegeven).

Vervolgens overschrijven we de instantiemethode toObjectom “name” in het geretourneerde object op te nemen (dit is een hoeksteen van objectserialisatie in fabric).

Ten slotte moeten we ook die fabric.NamedImage.fromObjectimplementeren die ik eerder noemde, zodat loadFromJSONweet welke methode moet worden aangeroepen tijdens JSON-parsering:

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

We laden hier een afbeelding (van “object.src”) en maken er vervolgens een instantie van fabric.NamedImagevan. Merk op dat de constructor op dat moment al voor de “naam”-instelling zorgt, omdat we de methode “initialiseren” eerder hebben overschreven.

En we moeten ook specificeren dat fabric.NamedImageeen asynchrone “klasse” is, wat betekent dat zijn fromObjectgeen instantie retourneert, maar deze doorgeeft aan een terugbelverzoek:

fabric.NamedImage.async = true;

En nu kunnen we dit allemaal uitproberen:

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

Antwoord 2, autoriteit 24%

Wauw. Mis ik hier iets?

Ik heb dit vaak gedaan en het heeft geen luxe subclassificatie nodig.

De documenten behandelen het: http://fabricjs.com/docs/fabric.Canvas .html#toJSON

Neem gewoon een reeks eigenschapnamen op als tekenreeksen in uw aanroep naar toJSON().

Bijv.

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

Hopelijk…. voor bonuspunten kun je een reviver-functie toevoegen die je aangepaste attributen rehydrateert.

Ook dit wordt behandeld in de documenten en heeft een voorbeeld.


Antwoord 3, autoriteit 4%

Ik had hetzelfde probleem, maar ik wilde de fabric.js-klassen niet uitbreiden.

Ik heb een functie geschreven die het canvas van de stof in parameter neemt en een stringified versie teruggeeft met mijn speciale attributen:

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

De speciale attributen lijken correct geïmporteerd als ik canvas.loadFromJSON()gebruik, ik gebruik fabric 1.7.2.


Antwoord 4, autoriteit 3%

Een eenvoudigere benadering zou zijn om de eigenschappen na stringify toe te voegen:

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

Antwoord 5, autoriteit 3%

Als u niet de aangepaste kenmerken wilt specificeren die u gebruikt elke keer dat u canvas.toJSON()aanroept, en u geen ingewikkelde subclasseringsbenadering wilt gebruiken, volgt hier een zeer eenvoudige manier om de toObject-methode van Fabric uit te breiden.

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

Vervolgens kunt u aangepaste eigenschappen instellen voor Fabric-objecten.

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

En wanneer u canvas.toJSON()aanroept, blijven deze eigenschappen behouden in de uitvoer. Als u vervolgens canvas.loadFromJSON()gebruikt met uw JSON-uitvoer, worden de aangepaste kenmerken geïmporteerd en toegepast op de Fabric-objecten.

Other episodes