Статьи

Fabric.js: продвинутый

Мы рассмотрели большинство основ Fabric в первой и второй частях этой серии. В этой статье я перейду к более продвинутым функциям: группам, сериализации (и десериализации) и классам.

группы

Первая тема, о которой я расскажу, — это группы, одна из самых мощных функций Fabric. Группы — это именно то, на что они похожи — простой способ сгруппировать объекты Fabric в единую сущность, чтобы вы могли работать с этими объектами как с единым целым. (См. Рисунок 1. )

Выбор становится группой в ткани
Рисунок 1. Выбор становится группой в ткани

Помните, что любое количество объектов Fabric на холсте можно сгруппировать с помощью мыши, чтобы сформировать единое выделение. После группировки объекты можно перемещать и даже изменять как единое целое. Вы можете масштабировать группу, поворачивать ее и даже изменять ее свойства представления — цвет, прозрачность, границы и т. Д.

Каждый раз, когда вы выбираете такие объекты на холсте, Fabric неявно создает группу за кулисами. Учитывая это, имеет смысл предоставлять доступ к группам только программным fabric.Group вступает fabric.Group .

Давайте создадим группу из двух объектов, круга и текста:

  var text = new fabric.Text ('Привет, мир', {
	   fontSize: 30
	 });
	 var circle = new fabric.Circle ({
	   радиус: 100,
	   заполните: '#eef',
	   шкала Y: 0,5
	 });
	 var group = new fabric.Group ([текст, круг], {
	   осталось: 150,
	   верх: 100,
	   угол: -10
	 });
	 canvas.add (группа); 

Сначала я создал текстовый объект «Привет мир». Затем я создал круг с радиусом 100 пикселей, заполненный цветом «#eef» и сжатый по вертикали (scaleY = 0.5). Затем я создал экземпляр fabric.Group , передав ему массив с этими двумя объектами и присвоив ему положение 150/100 под углом -10 градусов. Наконец, я добавил группу на холст, как и любой другой объект, используя canvas.add() .

Вуаля! Вы видите объект на холсте, как показано на рисунке 2 , помеченный эллипсом, и теперь можете работать с этим объектом как с одним объектом. Чтобы изменить этот объект, вы просто изменяете свойства группы, предоставляя ей собственные значения left, top и angle.

Группа, созданная программно
Рисунок 2 Группа, созданная программно

И теперь, когда у нас есть группа на холсте, давайте немного ее изменим:

  group.item (0) .set ({
	   текст: трололо,
	   заполнить: «белый»
	 });
	 group.item (1) .setFill ( 'красный'); 

Здесь мы получаем доступ к отдельным объектам в группе с помощью метода item и изменяем их свойства. Первый объект — это текст, а второй — сжатый круг. На рисунке 3 показаны результаты.

Выжатый красный круг с новым текстом
Рисунок 3. Сжатый красный круг с новым текстом

Одна важная идея, которую вы, вероятно, уже заметили, заключается в том, что все объекты в группе расположены относительно центра группы. Когда я изменил текстовое свойство текстового объекта, он остался центрированным, даже когда я изменил его ширину. Если вам не нужно это поведение, вам нужно указать координаты объекта слева / сверху, в этом случае они будут сгруппированы в соответствии с этими координатами.

Вот как создать и сгруппировать три круга так, чтобы они располагались горизонтально один за другим, как показано на рисунке 4 .

  var circle1 = новая ткань. Круг ({
	   радиус: 50,
	   заполнить: «красный»,
	   осталось: 0
	 });
	 var circle2 = новая ткань. Круг ({
	   радиус: 50,
	   заполнить: «зеленый»,
	   осталось: 100
	 });
	 var circle3 = новая ткань. Круг ({
	   радиус: 50,
	   заполнить: «синий»,
	   осталось: 200
	 });
	 var group = new fabric.Group ([circle1, circle2, circle3], {
	   слева: 200,
	   верх: 100
	 });
	 canvas.add (группа); 

Группа с тремя кругами, расположенными горизонтально
Рисунок 4. Группа с тремя окружностями, расположенными горизонтально

Еще один момент, который нужно учитывать при работе с группами, — это состояние объектов. Например, при формировании группы с изображениями необходимо убедиться, что эти изображения полностью загружены. Поскольку Fabric уже предоставляет вспомогательные методы для обеспечения загрузки изображения, эта операция становится довольно простой, как вы можете видеть в этом коде и на рисунке 5.

  fabric.Image.fromURL ('/ assets / pug.jpg', функция (img) {
	   var img1 = img.scale (0.1) .set ({слева: 100, сверху: 100});
	   fabric.Image.fromURL ('/ assets / pug.jpg', функция (img) {
	     var img2 = img.scale (0.1) .set ({слева: 175, сверху: 175});
	     fabric.Image.fromURL ('/ assets / pug.jpg', функция (img) {
	       var img3 = img.scale (0.1) .set ({слева: 250, сверху: 250});
	       canvas.add (новая fabric.Group ([img1, img2, img3],
	         {слева: 200, сверху: 200}))
	     });
	   });
	 }); 

Группа с тремя изображениями
Рисунок 5. Группа с тремя изображениями

Для работы с группами доступно несколько других методов:

  • getObjects работает точно так же, как fabric.Canvas # getObjects () и возвращает массив всех объектов в группе
  • размер представляет количество объектов в группе
  • Содержит позволяет проверить, находится ли конкретный объект в группе
  • элемент (который вы видели ранее) позволяет вам извлечь конкретный объект из группы
  • forEachObject также отражает fabric.Canvas # forEachObject, но по отношению к групповым объектам
  • добавить и удалить добавить и удалить объекты из группы, соответственно

Вы можете добавлять или удалять объекты с или без обновления размеров и положения группы. Вот несколько примеров:

Чтобы добавить прямоугольник в центр группы (слева = 0, сверху = 0), используйте этот код:

  group.add (new fabric.Rect ({
	 ...
	 })); 

Чтобы добавить прямоугольник в 100 пикселей от центра группы, сделайте это:

  group.add (new fabric.Rect ({
	   ...
	   осталось: 100,
	   верх: 100
	 })); 

Чтобы добавить прямоугольник в центр группы и обновить размеры группы, используйте следующий код:

  group.addWithUpdate (new fabric.Rect ({
	 ...
	 слева: group.getLeft (),
	 top: group.getTop ()
	 })); 

Чтобы добавить прямоугольник на расстоянии 100 пикселей от центра группы и обновить размеры группы, сделайте следующее:

  group.addWithUpdate (new fabric.Rect ({
	 ...
	 слева: group.getLeft () + 100,
	 top: group.getTop () + 100
	 })); 

Наконец, если вы хотите создать группу с объектами, которые уже присутствуют на холсте, вам нужно сначала их клонировать:

  // создаем группу с копиями существующих (2) объектов
	 var group = new fabric.Group ([
	   canvas.item (0) .clone (),
	   canvas.item (1) .clone ()
	 ]);
	 // удаляем все объекты и перерисовываем
	 canvas.clear () renderAll ().
	 // добавить группу на холст
	 canvas.add (группа); 

Сериализация

Как только вы начнете создавать какое-либо приложение с сохранением состояния — возможно, такое, которое позволит пользователям сохранять результаты содержимого холста на сервере или потоковое содержимое на другом клиенте — вам потребуется сериализация холста. Всегда есть возможность экспортировать холст в изображение, но загрузка большого изображения на сервер требует большой полосы пропускания. Ничто не сравнится с текстом, когда дело доходит до размера, и именно поэтому Fabric предоставляет отличную поддержку для сериализации и десериализации холста.

toObject, toJSON

Основой сериализации холста в Fabric являются fabric.Canvas#toObject и fabric.Canvas#toJSON . Давайте рассмотрим простой пример, сначала сериализовав пустой холст:

  var canvas = new fabric.Canvas ('c');
	 JSON.stringify (холст);  // '{"objects": [], "background": "rgba (0, 0, 0, 0)"}' 

Здесь я использую метод ES5 JSON.stringify , который неявно вызывает метод toJSON для переданного объекта, если этот метод существует. Поскольку экземпляр canvas в Fabric имеет метод toJSON, мы как бы вместо этого вызываем JSON.stringify(canvas.toJSON()) .

Обратите внимание на возвращенную строку, которая представляет пустой холст. Он в формате JSON и по существу состоит из свойств «objects» и «background». Свойство «objects» в настоящее время пусто, потому что на холсте ничего нет, а «background» имеет прозрачное значение по умолчанию («rgba (0, 0, 0, 0)»).

Давайте дадим нашему холсту другой фон и посмотрим, как все меняется:

  canvas.backgroundColor = 'red';
	 JSON.stringify (холст);  // '{"objects": [], "background": "red"}' 

Как и следовало ожидать, представление холста отражает новый цвет фона. Теперь давайте добавим несколько объектов:

  canvas.add (new fabric.Rect ({
	   осталось: 50,
	   верх: 50,
	   высота: 20,
	   ширина: 20,
	   заполнить: «зеленый»
	 }));
	 console.log (JSON.stringify (холст)); 

Зарегистрированный вывод выглядит следующим образом:

 '{"objects":[{"type":"rect","left":50,"top":50,"width":20,"height":20,"fill":"green","overlayFill":null, "stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false, "opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":false,"transparentCorners":true, "perPixelTargetFind":false,"rx":0,"ry":0}],"background":"rgba(0, 0, 0, 0)"}' 

Вот это да! На первый взгляд, многое изменилось, но если присмотреться, можно увидеть, что вновь добавленный объект теперь является частью массива «objects», сериализованного в JSON. Обратите внимание, как его представление включает в себя все его визуальные черты — левый, верхний, ширину, высоту, заливку, обводку и так далее.

Если бы мы добавили еще один объект, скажем, красный круг, расположенный рядом с прямоугольником, вы бы увидели, что представление изменилось соответствующим образом:

  canvas.add (новая ткань. Круг ({
	   осталось: 100,
	   верх: 100,
	   радиус: 50,
	   заполнить: «красный»
	 }));
	 console.log (JSON.stringify (холст)); 

Вот зарегистрированный вывод сейчас:

 '{"objects":[{"type":"rect","left":50,"top":50,"width":20,"height":20,"fill":"green","overlayFill":null, "stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false, "opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":false,"transparentCorners":true, "perPixelTargetFind":false,"rx":0,"ry":0},"type":"circle","left":100,"top":100,"width":100,"height":100,"fill":"red", "overlayFill":null,"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false, "flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":false, "transparentCorners":true,"perPixelTargetFind":false,"radius":50}],"background":"rgba(0, 0, 0, 0)"}' 

Обратите внимание на части «type»: «rect» и «type»: «circle», чтобы вы могли лучше видеть, где находятся эти объекты. Несмотря на то, что на первый взгляд может показаться, что вывод получился слишком большим, это ничто по сравнению с тем, что вы получите при сериализации изображений. Просто ради интереса, взгляните примерно на одну десятую (!) canvas.toDataURL('png') вы получите с canvas.toDataURL('png') :

 data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAK8CAYAAAAXo9vkAAAgAElEQVR4Xu3dP4xtBbnG4WPAQOQ2YBCLK1qpoQE1 /m+NVlCDwUACicRCEuysrOwkwcJgAglEItRQaWz9HxEaolSKtxCJ0FwMRIj32zqFcjm8e868s2fNWoJygl+e397rWetk5xf5pyZd13wPwIEC BAgQIAAAQIECBxI4F0H+hwfQ4AAAQIECBAgQIAAgQsCxENAgAABAgQIECBAgMDBBATIwah9EAECBAgQIECAAAECAsQzQIAAAQIECBAgQIDAw QQEyMGofRABAgQIECBAgAABAgLEM0CAAAECBAgQIECAwMEEBMjBqH0QAQIECBAgQIAAAQICxDNAgAABAgQIECBAgMDBBATIwah9EAECBAgQI ECAAAECAsQzQIAAAQIECBAgQIDAwQQEyMGofRABAgQIECBAgAABAgLEM0CAAAECBAgQIECAwMEEBMjBqH0QAQIECBAgQIAAAQICxDNAgAABA gQIECBAgMDBBATIwah9EAECBAgQIECAAAECAsQzQIAAAQIECBAgQIDAwQQEyMGofRABAgQIECBAgAABAgLEM0CAAAECBAgQIECAwMEEBMjBq H0QAQIECBAgQIAAAQICxDNAgAABAgQIECBAgMDBBATIwah9EAECBAgQIECAAAECAsQzQIAAAQIECBAgQIDAwQQEyMGofRABAgQIECBAgAABA gLEM0CAAAECBAgQIECAwMEEBMjBqH0QAQIECBAgQIAAAQICxDNAgAABAgQIECBAgMDBBATIwah9EAECBAgQIECAAAECAsQzQIAAAQIECBAgQ IDAwQQEyMGofRABAgQIECBAgAABAgLEM0CAAAECBAgQIECAwMEEBMjBqH0QAQIECBAgQIAAAQICxDNAgAABAgQIECBAgMDBBATIwah9EAECB AgQIECAAAECAsQzQIAAAQIECBAgQIDAwQQEyMGofRABAgQIECBAgAABAgLEM0CAAAECBAgQIECAwMEEBMjBqH0QAQIECBAgQIAAAQICxDNAg AABAgQIECBAgMDBBATIwah9EAECBAgQIECAAAECyw+Qb134RU2fevC8q+5esGWESBAgAABAgQIEFiOwPLMC5AlvO0OBMCBAgQIECAAAECJxQ QICcE9HYCBAgQIECAAAECBPYXECD7W3klAQIECBAgQIAAAQInFBAgJwT0dgIECBAgQIAAAQIE9hcQIPtbeSUBAgQIECBAgAABAicUECAnBPR 2AgQIECBAgAABAgT2FxAg+1t5JQECBAgQIECAAAECJxQQICcE9HYCBAgQIECAAAECBPYXECD7W3klAQIECBAgQIAAAQInFBAgJwTc9+3z49y vmNd+dI7PzPHJOW6Y4wNzXD3HlXNc9pZdb85/vzbHK3P8aY7n5vj1HL+Y43dz417f97O9jgABAgQIECBAgMBSBATIKd2JCY5dWNwyx5fn+Pw cV5U/6tXZ99M5fjjHk3Mjd6HifwQIECBAgAABAgQWLSBAirdnouP6WXfvHHfOcU1x9T6rXp4XPTLHA3NTX9jnDV5DgAABAgQIECBA4NACAuS E4hMdl8+Kr83xzTmuO+G61ttfnEXfnuN7c4PfaC21hwABAgQIECBAgMBJBQTIJQpOeFw7b71/jtsvccWh3vbYfNB9c6NfOtQH+hwCBAgQIEC AAAECFxMQIMd8No7C4+F5283HfOtZv/ypOYG7hMhZ3wafT4AAAQIECBDYtoAA2fP+H/1Vqwd3f4jf8y1Lfdkunu7xV7OWenucFwECBAgQIEB g3QICZI/7O/Fxx7xs9wf3t36r3D3evciX7L7F7+6rIY8u8uycFAECBAgQIE 

… и еще примерно 17 000 символов.

Вы можете быть удивлены, почему существует также fabric.Canvas#toObject. Проще говоря, toObject возвращает то же представление, что и toJSON, только в форме фактического объекта, без сериализации строк. Например, используя более ранний пример холста с зеленым прямоугольником, вывод для canvas.toObject выглядит следующим образом:

  {"background": "rgba (0, 0, 0, 0)",
	   "объекты" : [
	     {
	       «угол»: 0,
	       «заполнить»: «зеленый»,
	       "flipX": ложь,
	       "flipY": ложь,
	       "hasBorders": правда,
	       "hasControls": правда,
	       "hasRotatingPoint": ложь,
	       «высота»: 20,
	       «слева»: 50,
	       «непрозрачность»: 1,
	       "overlayFill": ноль,
	       "perPixelTargetFind": ложь,
	       «scaleX»: 1,
	       «scaleY»: 1,
	       «выбираемый»: правда,
	       «ход»: ноль,
	       "strokeDashArray": ноль,
	       "ширина хода": 1,
	       «верх»: 50,
	       "transparentCorners": правда,
	       "type": "rect",
	       "ширина": 20
	     }
	   ]
	 } 

Как вы можете видеть, вывод toJSON по существу является строковым toObject . Теперь интересная (и полезная) вещь заключается в том, что вывод toObject является умным и ленивым. То, что вы видите внутри массива «objects», является результатом итерации по всем объектам canvas и делегирования каждому методу toObject каждого объекта. Например, fabric.Path имеет свой собственный toObject который знает, что возвращает массив «points» пути, и fabric.Image имеет toObject который знает, что возвращает свойство «src» изображения. В истинно объектно-ориентированном виде все объекты способны к сериализации.

Это означает, что когда вы создаете свой собственный класс или просто хотите настроить сериализованное представление объекта, все, что вам нужно сделать, — это работать с методом toObject , либо полностью заменяя его, либо расширяя его. Вот пример:

  var rect = new fabric.Rect ();
	 rect.toObject = function () {
	   return {имя: 'трололо'};
	 };
	 canvas.add (прямоугольник);
	 console.log (JSON.stringify (холст)); 

Зарегистрированный вывод:

 '{"objects":[{"name":"trololo"}],"background":"rgba(0, 0, 0, 0)"}' 

Как видите, у массива объектов теперь есть собственное представление нашего прямоугольника. Этот тип переопределения приносит смысл, но, вероятно, не очень полезен. Вместо этого, вот как расширить метод toObject прямоугольника дополнительным свойством:

  var rect = new fabric.Rect ();
	 rect.toObject = (function (toObject) {
	   return function () {
	     вернуть fabric.util.object.extend (toObject.call (this), {
	       имя: это.имя
	     });
	   };
	 }) (Rect.toObject);
	 canvas.add (прямоугольник);
	 rect.name = 'trololo';
	 console.log (JSON.stringify (холст)); 

И вот зарегистрированный вывод:

 '{"objects":[{"type":"rect","left":0,"top":0,"width":0,"height":0,"fill":"rgb(0,0,0)","overlayFill":null, "stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":0,"flipX":false, "flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":false, "transparentCorners":true,"perPixelTargetFind":false,"rx":0,"ry":0,"name":"trololo"}], "background":"rgba(0, 0, 0, 0)"}' 

Я расширил существующий метод toObject объекта дополнительным свойством «name», что означает, что свойство теперь является частью вывода toObject , и в результате оно появляется в JSON-представлении canvas. Еще один момент, о котором стоит упомянуть, это то, что если вы расширяете такие объекты, вы также должны быть уверены, что «класс» fabric.Rect (в данном случае fabric.Rect ) имеет это свойство в массиве «stateProperties», чтобы загрузка холста из строковое представление проанализирует и добавит его к объекту правильно.

toSVG

Еще одно эффективное текстовое представление холста в формате SVG. Поскольку Fabric специализируется на анализе и рендеринге SVG на холсте, имеет смысл сделать это двусторонним процессом и обеспечить преобразование холста в SVG. Давайте добавим этот же прямоугольник к нашему холсту и посмотрим, какое представление возвращается из метода toSVG :

  canvas.add (new fabric.Rect ({
	   осталось: 50,
	   верх: 50,
	   высота: 20,
	   ширина: 20,
	   заполнить: «зеленый»
	 }));
	 console.log (canvas.toSVG ()); 

Зарегистрированный вывод выглядит следующим образом:

 '<?xml version="1.0" standalone="no" ?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800" height="700" xml:space="preserve"><desc>Created with Fabric.js 0.9.21</desc><rect x="-10" y="-10" rx="0" ry="0" width="20" height="20" style="stroke: none; stroke-width: 1; stroke-dasharray: ; fill: green; opacity: 1;" transform="translate(50 50)" /></svg>' 

Как и в случае с toJSON и toObject , метод toSVG — при вызове на холсте — делегирует свою логику каждому отдельному объекту, и у каждого отдельного объекта есть свой собственный метод toSVG , особенный для типа объекта. Если вам когда-либо понадобится изменить или расширить SVG-представление объекта, вы можете сделать с toSVG то же самое, что я делал ранее с toObject .

Преимущество представления SVG по сравнению с фирменным toObject / toJSON в Fabric toObject toJSON , что вы можете toJSON его в любой SVG-совместимый рендерер (браузер, приложение, принтер, камера и т. Д.), И он должен просто работать. Однако с помощью toObject / toJSON вам сначала нужно загрузить его на холст.

И если говорить о загрузке вещей на холст, теперь, когда вы знаете, как сериализовать холст в эффективный фрагмент текста, как вы собираетесь загружать эти данные обратно на холст?

Десериализация и парсер SVG

Как и в случае с сериализацией, существует два способа загрузки холста из строки: из представления JSON или из SVG. При использовании представления JSON используются методы fabric.Canvas#loadFromJSON и fabric.Canvas#loadFromDatalessJSON . При использовании SVG существуют fabric.loadSVGFromURL и fabric.loadSVGFromString .

Обратите внимание, что первые два метода являются методами экземпляра и вызываются непосредственно для экземпляра холста, тогда как два других метода являются статическими методами и вызываются для объекта «fabric», а не для canvas.

Там не так много, чтобы сказать о большинстве этих методов. Они работают точно так, как вы ожидаете. Давайте возьмем в качестве примера предыдущий вывод JSON с нашего холста и загрузим его на чистый холст:

  var canvas = new fabric.Canvas ();
	 canvas.loadFromJSON ( '{ "объекты": [{ "типа": "Прямоугольник", "влево": 50, "верх": 50, "ширина": 20, "Высота": 20, 
 заливка ":" зеленый», "overlayFill": нулевой, "инсульт": нулевой, "strokeWidth": 1, "StrokeDashArray": нулевой, "Scalex": 1,
 "ScaleY": 1, "угол": 0, "flipX" ложь "flipY" ложь "прозрачность": 1, "по выбору": правда, "HasControls": истинные,
 "hasBorders": правда, "hasRotatingPoint": ложные "transparentCorners": правда, "perPixelTargetFind": ложь,
 "Гх": 0, "гу": 0}, "Тип": "круг", "влево": 100, "верх": 100, "ширина": 100, "высота": 100, "заливка":» красный»,
 "OverlayFill": нулевой, "инсульт": нулевой, "strokeWidth": 1, "StrokeDashArray": нулевой, "Scalex": 1, "ScaleY": 1,
 "Угол": 0, "flipX" ложь "flipY" ложь "прозрачность": 1, "по выбору": истинный "HasControls": правда,
 "hasBorders": правда, "hasRotatingPoint": ложные "transparentCorners": правда, "perPixelTargetFind": ложь,
 "radius": 50}], "background": "rgba (0, 0, 0, 0)"} '); 

Оба объекта волшебным образом появляются на холсте, как показано на рисунке 6 .

Круг и квадрат на холсте
Рисунок 6. Круг и квадрат на холсте

Таким образом, загрузка canvas из строки довольно проста, но как насчет этого странно выглядящего метода loadFromDatalessJSON ? Чем он отличается от loadFromJSON , который мы только что использовали? Чтобы понять, зачем вам нужен этот метод, посмотрите на сериализованный холст с более или менее сложным объектом пути, подобным показанному на рисунке 7 .

Сложная форма на холсте
Рисунок 7. Сложная форма, отображаемая на холсте

Вывод JSON.stringify (canvas) для фигуры на рисунке 7 выглядит следующим образом:

 {"objects":[{"type":"path","left":184,"top":177,"width":175,"height":151,"fill":"#231F20","overlayFill":null, "stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":-19,"flipX":false, "flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":false, "transparentCorners":true,"perPixelTargetFind":false,"path":[["M",39.502,61.823],["c",-1.235,-0.902,-3.038, -3.605,-3.038,-3.605],["s",0.702,0.4,3.907,1.203],["c",3.205,0.8,7.444,-0.668,10.114,-1.97],["c",2.671,-1.302, 7.11,-1.436,9.448,-1.336],["c",2.336,0.101,4.707,0.602,4.373,2.036],["c",-0.334,1.437,-5.742,3.94,-5.742,3.94], ["s",0.4,0.334,1.236,0.334],["c",0.833,0,6.075,-1.403,6.542,-4.173],["s",-1.802,-8.377,-3.272,-9.013],["c",-1.468, -0.633,-4.172,0,-4.172,0],["c",4.039,1.438,4.941,6.176,4.941,6.176],["c",-2.604,-1.504,-9.279,-1.234,-12.619, 0.501],["c",-3.337,1.736,-8.379,2.67,-10.083,2.503],["c",-1.701,-0.167,-3.571,-1.036,-3.571,-1.036],["c",1.837, 0.034,3.239,-2.669,3.239,-2.669],["s",-2.068,2.269,-5.542,0.434],["c",-3.47,-1.837,-1.704,-8.18,-1.704,-8.18], ["s",-2.937,5.909,-1,9.816],["C",34.496,60.688,39.502,61.823,39.502,61.823],["z"],["M",77.002,40.772],["c",0,0, -1.78,-5.03,-2.804,-8.546],["l",-1.557,8.411],["l",1.646,1.602],["c",0,0,0,-0.622,-0.668,-1.691],["C",72.952, 39.48,76.513,40.371,77.002,40.772],["z"],["M",102.989,86.943],["M",102.396,86.424],["c",0.25,0.22,0.447,0.391, 0.594,0.519],["C",102.796,86.774,102.571,86.578,102.396,86.424],["z"],["M",169.407,119.374],["c",-0.09,-5.429, -3.917,-3.914,-3.917,-2.402],["c",0,0,-11.396,1.603,-13.086,-6.677],["c",0,0,3.56,-5.43,1.69,-12.461],["c", -0.575,-2.163,-1.691,-5.337,-3.637,-8.605],["c",11.104,2.121,21.701,-5.08,19.038,-15.519],["c",-3.34,-13.087, -19.63,-9.481,-24.437,-9.349],["c",-4.809,0.135,-13.486,-2.002,-8.011,-11.618],["c",5.473,-9.613,18.024,-5.874, 18.024,-5.874],["c",-2.136,0.668,-4.674,4.807,-4.674,4.807],["c",9.748,-6.811,22.301,4.541,22.301,4.541],["c", -3.097,-13.678,-23.153,-14.636,-30.041,-12.635],["c",-4.286,-0.377,-5.241,-3.391,-3.073,-6.637],["c",2.314, -3.473,10.503,-13.976,10.503,-13.976],["s",-2.048,2.046,-6.231,4.005],["c",-4.184,1.96,-6.321,-2.227,-4.362, -6.854],["c",1.96,-4.627,8.191,-16.559,8.191,-16.559],["c",-1.96,3.207,-24.571,31.247,-21.723,26.707],["c", 2.85,-4.541,5.253,-11.93,5.253,-11.93],["c",-2.849,6.943,-22.434,25.283,-30.713,34.274],["s",-5.786,19.583, -4.005,21.987],["c",0.43,0.58,0.601,0.972,0.62,1.232],["c",-4.868,-3.052,-3.884,-13.936,-0.264,-19.66],["c", 3.829,-6.053,18.427,-20.207,18.427,-20.207],["v",-1.336],["c",0,0,0.444,-1.513,-0.089,-0.444],["c",-0.535, 1.068,-3.65,1.245,-3.384,-0.889],["c",0.268,-2.137,-0.356,-8.549,-0.356,-8.549],["s",-1.157,5.789,-2.758, 5.61],["c",-1.603,-0.179,-2.493,-2.672,-2.405,-5.432],["c",0.089,-2.758,-1.157,-9.702,-1.157,-9.702],["c", -0.8,11.75,-8.277,8.011,-8.277,3.74],["c",0,-4.274,-4.541,-12.82,-4.541,-12.82],["s",2.403,14.421,-1.336, 14.421],["c",-3.737,0,-6.944,-5.074,-9.879,-9.882],["C",78.161,5.874,68.279,0,68.279,0],["c",13.428,16.088, 17.656,32.111,18.397,44.512],["c",-1.793,0.422,-2.908,2.224,-2.908,2.224],["c",0.356,-2.847,-0.624,-7.745, -1.245,-9.882],["c",-0.624,-2.137,-1.159,-9.168,-1.159,-9.168],["c",0,2.67,-0.979,5.253,-2.048,9.079],["c", -1.068,3.828,-0.801,6.054,-0.801,6.054],["c",-1.068,-2.227,-4.271,-2.137,-4.271,-2.137],["c",1.336,1.783, 0.177,2.493,0.177,2.493],["s",0,0,-1.424,-1.601],["c",-1.424,-1.603,-3.473,-0.981,-3.384,0.265],["c",0.089, 1.247,0,1.959,-2.849,1.959],["c",-2.846,0,-5.874,-3.47,-9.078,-3.116],["c",-3.206,0.356,-5.521,2.137,-5.698, 6.678],["c",-0.179,4.541,1.869,5.251,1.869,5.251],["c",-0.801,-0.443,-0.891,-1.067,-0.891,-3.473],... 

… и это только 20 процентов от всей продукции!

Что тут происходит? Что ж, получается, что этот fabric.Path экземпляр — эта форма — состоит буквально из сотен линий Безье, определяющих, как именно это должно быть отображено. Все эти [“c”, 0,2.67, -0.979,5.253, -2.048,9.079] куски в представлении JSON соответствуют каждой из этих кривых. И когда их сотни (или даже тысячи), представление холста оказывается довольно огромным.

Ситуации, подобные этим, fabric.Canvas#toDatalessJSON . Давай попробуем:

  canvas.item (0) .sourcePath = '/assets/dragon.svg';
	 console.log (JSON.stringify (canvas.toDatalessJSON ())); 

Вот зарегистрированный вывод:

 {"objects":[{"type":"path","left":143,"top":143,"width":175,"height":151,"fill":"#231F20","overlayFill":null, "stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":-19,"flipX":false, "flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":false, "transparentCorners":true,"perPixelTargetFind":false,"path":"/assets/dragon.svg"}],"background":"rgba(0, 0, 0, 0)"} 

Это, конечно, меньше, так что случилось? Обратите внимание, что перед вызовом toDatalessJSO N я дал объекту пути (в форме дракона) свойство sourcePat h объекта «/assets/dragon.svg». Затем, когда я вызвал toDatalessJSON , вся огромная строка пути из предыдущего вывода (эти сотни команд пути) заменяется одной строкой «dragon.svg».

Когда вы работаете со множеством сложных фигур, toDatalessJSON позволяет еще больше сократить представление холста и заменить представления данных огромного пути простой ссылкой на SVG.

Вы, вероятно, можете догадаться, что метод loadFromDatalessJSON просто позволяет загрузить холст из версии представления холста без данных. Метод loadFromDatalessJSON значительной степени знает, как взять эти строки «пути» (например, «/assets/dragon.svg»), загрузить их и использовать их в качестве данных для соответствующих объектов пути.

Теперь давайте посмотрим на методы загрузки SVG. Мы можем использовать либо строку, либо URL. Давайте сначала посмотрим на пример строки:

  fabric.loadSVGFromString ('...', функция (объекты, опции) {
	 var obj = fabric.util.groupSVGElements (объекты, опции);
	 canvas.add (OBJ) .renderAll ();
	 }); 

Первый аргумент — это строка SVG, а второй — функция обратного вызова. Обратный вызов вызывается, когда SVG анализируется и загружается и получает два аргумента — объекты и параметры. Первый, objects, содержит массив объектов, проанализированных из SVG — пути, группы путей (для сложных объектов), изображения, текст и т. Д. Чтобы сгруппировать эти объекты в единую коллекцию и сделать так, чтобы они выглядели так же, как в SVG-документе, мы используем fabric.util.groupSVGElement s и fabric.util.groupSVGElement ему как объекты, так и параметры. Взамен мы получаем экземпляр fabric.Path или fabric.PathGroup , который мы затем можем добавить на наш холст.

Метод fabric.loadSVGFromURL работает аналогичным образом, за исключением того, что вы передаете строку, содержащую URL, а не содержимое SVG. Обратите внимание, что Fabric попытается получить этот URL через XMLHttpRequest, поэтому SVG должен соответствовать обычным правилам SOP.

подклассов

Поскольку Fabric построен по-настоящему объектно-ориентированным способом, он разработан, чтобы сделать подклассы и расширения простыми и естественными. Как описано в первой статье этой серии, в Fabric существует иерархия объектов. Все двумерные объекты (пути, изображения, текст и т. Д.) fabric.Object от fabric.Object , а некоторые «классы» — как fabric.PathGroup — даже образуют наследование третьего уровня.

Итак, как вы подклассифицируете один из существующих «классов» в Fabric или, возможно, даже создаете свой собственный класс?

Для этой задачи вам понадобится служебный метод fabric.util.createClass . Этот метод — не более чем простая абстракция прототипного наследования JavaScript. Давайте сначала создадим простой Point «class»:

  var Point = fabric.util.createClass ({
	   initialize: function (x, y) {
	     this.x = x ||  0;
	     this.y = y ||  0;
	   },
	   toString: function () {
	     вернуть this.x + '/' + this.y;
	   }
	 }); 

Метод createClass принимает объект и использует свойства этого объекта для создания класса со свойствами уровня экземпляра. Единственное специально обработанное свойство — инициализация, которая используется в качестве конструктора. Теперь при инициализации Point мы создадим экземпляр со свойствами x и y и методом toString :

  точка вар = новая точка (10, 20);
	 point.x;  // 10
	 point.y;  // 20
	 point.toString ();  // "10/20" 

Если бы мы хотели создать дочерний элемент класса «Point», скажем, цветной точки, мы бы использовали createClass следующим образом:

  var ColoredPoint = fabric.util.createClass (Point, {
	   initialize: function (x, y, color) {
	     this.callSuper ('initialize', x, y);
	     this.color = color ||  '# 000';
	   },
	   toString: function () {
	     return this.callSuper ('toString') + '(color:' + this.color + ')';
	   }
	 }); 

Обратите внимание, как объект со свойствами уровня экземпляра теперь передается как второй аргумент. И первый аргумент получает Point «class», который говорит createClass использовать его как родительский класс этого класса. Чтобы избежать дублирования, мы используем метод callSuper , который вызывает метод родительского класса. Это означает, что если мы изменим Point , изменения также распространятся на класс ColoredPoint .

Вот ColoredPoint в действии:

  var redPoint = new ColoredPoint (15, 33, '# f55');
	 redPoint.x;  // 15
	 redPoint.y;  // 33
	 redPoint.color;  // "# f55"
	 redPoint.toString ();  «15/35 (цвет: # f55)» 

Теперь посмотрим, как работать с существующими классами Fabric. Например, давайте создадим класс LabeledRect , который по сути будет прямоугольником, с которым связана какая-то метка. При отображении на нашем холсте эта метка будет представлена ​​в виде текста внутри прямоугольника (аналогично предыдущему примеру группы с кружком и текстом). Работая с Fabric, вы заметите, что подобные комбинированные абстракции могут быть достигнуты либо с помощью групп, либо с помощью пользовательских классов.

  var LabeledRect = fabric.util.createClass (fabric.Rect, {
	   тип: 'labeleledRect',
	   initialize: function (options) {
	     варианты ||  (параметры = {});
	     this.callSuper ('initialize', options);
	     this.set ('label', options.label || '');
	   },
	   toObject: function () {
	     return fabric.util.object.extend (this.callSuper ('toObject'), {
	       label: this.get ('label')
	     });
	   },
	   _render: function (ctx) {
	     this.callSuper ('_ render', ctx);
	     ctx.font = '20px Helvetica';
	     ctx.fillStyle = '# 333';
	     ctx.fillText (this.label, -this.width / 2, -th..height / 2 + 20);
	   }
	 }); 

Похоже, здесь происходит довольно много, но на самом деле все довольно просто. Во-первых, мы указываем родительский класс как fabric.Rect , чтобы использовать его возможности рендеринга. Далее мы определяем свойство типа, устанавливая его как « labeledRect ». Это просто для согласованности, потому что все объекты Fabric имеют свойство type (rect, circle, path, text и т. Д.). Затем есть уже знакомый конструктор (initialize), в котором мы снова используем callSuper . Кроме того, мы устанавливаем метку объекта в зависимости от того, какое значение было передано через опции. Наконец, у нас осталось два метода — toObject и _render . Метод toObjec t, как вы уже знаете из раздела сериализации, отвечает за представление объекта (и JSON) экземпляра. Поскольку LabeledRect имеет те же свойства, что и обычный rect но и метку, мы расширяем метод toObject родительского toObject и просто добавляем в него метку. Наконец, что не менее _render метод _render отвечает за фактическое рисование экземпляра. В нем есть еще callSuper вызов callSuper , который отображает прямоугольник, и три дополнительных строки логики рендеринга текста.

Если вы должны визуализировать такой объект, вы делаете что-то вроде следующего. Рисунок 8 показывает результаты.

  var labeledRect = new LabeledRect ({
	   ширина: 100,
	   высота: 50,
	   осталось: 100,
	   верх: 100,
	   ярлык: «тест»,
	   заполните: '#faa'
	 });
	 canvas.add (labeledRect); 

Рендеринг с пометкойRect
Рисунок 8. Рендеринг labeleledRect

Изменение значения метки или любого другого обычного свойства прямоугольника, очевидно, будет работать как положено, как вы можете видеть здесь и на рисунке 9 .

  labeledRect.set ({
	   ярлык: «трололо»,
	   заполните: '#aaf',
	   rx: 10,
	   ры: 10
	 } 

Модифицированный помеченныйRect
Рисунок 9. Модифицированный помеченный Rect

Конечно, на этом этапе вы можете изменять поведение этого класса в любом случае. Например, вы можете сделать определенные значения значениями по умолчанию, чтобы избежать каждой их передачи конструктору, или вы можете сделать определенные настраиваемые свойства доступными в экземпляре. Если вы сделаете дополнительные свойства настраиваемыми, вы можете учесть их в toObject и initialize , как я показал здесь:

  ...
	 initialize: function (options) {
	   варианты ||  (параметры = {});
	   this.callSuper ('initialize', options);
	   // дать всем помеченным прямоугольникам фиксированную ширину / высоту 100/50
	   this.set ({ширина: 100, высота: 50});
	   this.set ('label', options.label || '');
	 }
	 ...
	 _render: function (ctx) {
	   // сделать шрифт и заполнить значения меток настраиваемыми
	   ctx.font = this.labelFont;
	   ctx.fillStyle = this.labelFill;
	   ctx.fillText (this.label, -this.width / 2, -th..height / 2 + 20);
	 }
	 ... 

Завершение

Это завершает третью часть этой серии, в которой я углубился в некоторые из более сложных аспектов Fabric. С помощью групп, сериализации, десериализации и классов вы можете вывести свое приложение на совершенно новый уровень.