Fabric.js — как сохранить холст на сервере с настраиваемыми атрибутами

Я хотел бы иметь возможность сохранить текущее состояние холста в базе данных на стороне сервера, возможно, в виде строки 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 (конструктор), чтобы также установить свойство «имя» при инициализации объекта (если это свойство задано).

Затем мы перезаписываем метод экземпляра 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. Обратите внимание, что в этот момент конструктор уже позаботится о настройке «имя», поскольку ранее мы перезаписали метод «инициализировать».

И нам также нужно указать, что 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 /выпуски/1826. Без «типа» вызывается неправильный 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
Я создал объект Custom Line 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

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

Если вы не хотите указывать настраиваемые атрибуты, которые вы используете каждый раз, когда вызываете canvas.toJSON(), и вы не хотите использовать сложный подход к созданию подклассов, вот очень простой способ расширить метод Fabric 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);

Затем вы можете установить пользовательские свойства для объектов Fabric.

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

И когда вы вызываете canvas.toJSON(), эти свойства сохранятся на выходе. Если вы затем используете canvas.loadFromJSON() с выводом JSON, настраиваемые атрибуты будут импортированы и применены к объектам Fabric.

person Neil VanLandingham    schedule 30.05.2019