Fabric.js - как да запазите canvas на сървър с потребителски атрибути

Бих искал да мога да запазя текущото състояние на canvas в база данни от страната на сървъра, вероятно като 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 низ, изпратен от сървъра, и възстановеното платно също да включва моя персонализиран атрибути? Благодаря.


person sa125    schedule 30.06.2012    source източник


Отговори (5)


Добър въпрос.

Ако добавяте персонализирани свойства към обекти, тези обекти вероятно са „специални“ по някакъв начин. Изглежда, че подкласирането им би било разумно решение.

Например, ето как бихме подкласирали fabric.Image в наименувано изображение. След това тези обекти на изображения могат да имат имена като "Гандалф" или "Самуайз".

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

И накрая, ще трябва също така да внедрим този 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);
});
person kangax    schedule 30.06.2012
comment
Всъщност получавам грешка, когато се опитвам да заредя обратно платното от JSON низа. Използвам canvas.loadFromDatalessJSON(json), който преди това работеше с fabric.Image. Сега, когато използвам fabric.NamedImage, както е предложено по-горе, получавам cannot call method 'setupState' of undefined. Как мога да поправя това? - person sa125; 01.07.2012
comment
Изглежда, че има грешка в toDatalessJSON :/ Имаме някаква дублираща логика в toJSON и toDatalessJSON и искам да пренапиша всичко това, като го направя хубаво, чисто и последователно. Ще го направя някъде в близко бъдеще. - person kangax; 04.07.2012
comment
Независимо от текущата имплементация, изглежда, че всъщност самият аз бърках нещата - вижте това: stackoverflow.com/a/11298971/187907 - person sa125; 05.07.2012
comment
Главата за сериализацията (fabricjs.com/fabric-intro-part-3/#serialization) може да се актуализира с това. Съществуващият текст има грешки (напр. не съхранява подклас в обект на тъканта, не използва низове с тирета). - person Chris; 04.06.2013
comment
Вижте другия отговор на @kangax на подобен въпрос: stackoverflow.com/questions/19444196/ - person Michael J. Calkins; 18.01.2014
comment
@kangax, това е наистина страхотно, но се боря да направя същото нещо (да добавя име) към подклас на fabric.Group, изглежда, че работи до точката, в която мога да запазя JSON, но при зареждане получавам Uncaught TypeError: Cannot read property 'async' of undefined аз съм доста нови за JS и затова вероятно са сбъркали fromObject();. случайно да знаеш подписа за това? - person DazBaldwin; 14.04.2014
comment
добре, сортирах това (това беше форматът на моя type:, но сега имам различен проблем, както е показано тук: stackoverflow.com/questions/23076344/ - person DazBaldwin; 15.04.2014
comment
Не работи за PathGroup - проблемът е описан тук: github.com/kangax/fabric.js /issues/1826. Без „type“ се извиква грешният fromObject и ние не получаваме персонализираното свойство. - person Sava B.; 07.04.2015
comment
@kangax, ако искаш да създадеш нов филтър, трябва ли да го приложиш към NamedImage или Image? Не мога да накарам JSON.stringify(canvas) да работи, когато персонализираният ми филтър е приложен към Изображение. - person Wissem; 19.05.2015
comment
Благодаря Ви за отговора! Това ми помогна. Разширих fabric.Image със собствен клас и се борех с правилната сериализация/десериализация. Може ли някой да обясни защо fabric.util.loadImage() работи, но fabric.Image.fromObject() се проваля? Издаде грешка при опит за достъп до src свойство на недефинирано originalelement - person nned; 10.06.2016
comment
Бях създал потребителски редов обект GridLine и като върнах {} от toObject(), успях да държа далеч обектите от състоянията на хронологията. - person kuldipem; 21.10.2016

Еха. Пропускам ли нещо тук?

Правил съм това много пъти и не се нуждае от някакво фантастично подкласиране.

Документите го покриват: http://fabricjs.com/docs/fabric.Canvas.html#toJSON

Просто включете масив от имена на свойства като низове във вашето извикване на toJSON().

Eg

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

Надяваме се.... за бонус точки можете да добавите функция за съживяване, която ще рехидратира вашите потребителски атрибути.

Отново това е описано в документите и има пример.

person kbcool    schedule 21.04.2018
comment
Сега разбирам, че този въпрос е от 6 години LOL. Както и да е, надявам се да помогне на всеки, който се натъкне на него. - person kbcool; 21.04.2018
comment
за мен canvas.toJSON върна обект, а не низ. Трябваше също да извикам JSON.stringify. - person ps0604; 03.07.2019

Имах същия проблем, но не исках да разширя класовете 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.

person adrien54    schedule 13.01.2017

По-прост подход би бил да добавите свойствата след 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);
person Dan Ochiana    schedule 17.09.2014
comment
Обратното трябва да работи без никакви проблеми: github.com/kangax/fabric.js/issues/ 471 - person xmojmr; 08.02.2015

Ако не искате да указвате персонализираните атрибути, които използвате всеки път, когато извиквате canvas.toJSON(), и не искате да използвате сложен подход за подкласиране, ето един много прост начин за разширяване на метода toObject на Fabric.

//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() тези свойства ще се запазят в изхода. Ако след това използвате canvas.loadFromJSON() с вашия JSON изход, персонализираните атрибути ще бъдат импортирани и приложени към обектите Fabric.

person Neil VanLandingham    schedule 30.05.2019