fabric.Group
. var circle = new fabric.Circle({ radius: 100, fill: '#eef', scaleY: 0.5, originX: 'center', originY: 'center' }); var text = new fabric.Text('hello world', { fontSize: 30, originX: 'center', originY: 'center' }); var group = new fabric.Group([ circle, text ], { left: 150, top: 100, angle: -10 }); canvas.add(group);
originX
and originY
properties have originX
originY
to 'center'
, which will 'center'
this object within the group. At initial settings, group members are oriented relative to the upper left corner of the group. Then a circle with a radius of 100px was filled with the color "#eef" and squeezed vertically (compression ratio - 0.5). Next, create a fabric.Group
object with two parameters. The first parameter is an array of our 2 objects. The second parameter set the group position 150/100 and the angle -10. Finally, they added it using the canvas.add()
method like any other object. group.item(0).setFill('red'); group.item(1).set({ text: 'trololo', fill: 'white' }); canvas.renderAll();
item()
method and changed their properties. The first object is a compressed circle, the second is text. Let's see what happened: var circle1 = new fabric.Circle({ radius: 50, fill: 'red', left: 0 }); var circle2 = new fabric.Circle({ radius: 50, fill: 'green', left: 100 }); var circle3 = new fabric.Circle({ radius: 50, fill: 'blue', left: 200 }); var group = new fabric.Group([ circle1, circle2, circle3 ], { left: 200, top: 100 }); canvas.add(group);
fabric.Image.fromURL('/assets/pug.jpg', function(img) { var img1 = img.scale(0.1).set({ left: 100, top: 100 }); fabric.Image.fromURL('/assets/pug.jpg', function(img) { var img2 = img.scale(0.1).set({ left: 175, top: 175 }); fabric.Image.fromURL('/assets/pug.jpg', function(img) { var img3 = img.scale(0.1).set({ left: 250, top: 250 }); canvas.add(new fabric.Group([ img1, img2, img3], { left: 200, top: 200 })) }); }); });
getObjects()
method, which also works like fabric.Canvas#getObjects()
, and returns an array of all the objects in the group. There is a size()
method that shows the number of all objects in the group. There is also a contains()
method that checks for the presence of a specific object in the group. The previously mentioned item()
method, which allows you to take a specific object from a group. The forEachObject()
method, which works just like fabric.Canvas#forEachObject
, with groups only. And finally, the add()
and remove()
methods, which respectively add and remove objects from the group. group.add(new fabric.Rect({ ... originX: 'center', originY: 'center' }));
group.add(new fabric.Rect({ ... left: 100, top: 100, originX: 'center', originY: 'center' }));
group.addWithUpdate(new fabric.Rect({ ... left: group.getLeft(), top: group.getTop(), originX: 'center', originY: 'center' }));
group.addWithUpdate(new fabric.Rect({ ... left: group.getLeft() + 100, top: group.getTop() + 100, originX: 'center', originY: 'center' }));
// 2- . var group = new fabric.Group([ canvas.item(0).clone(), canvas.item(1).clone() ]); // . canvas.clear().renderAll(); // canvas. canvas.add(group);
fabric.Canvas#toObject()
and fabric.Canvas#toJSON()
. var canvas = new fabric.Canvas('c'); JSON.stringify(canvas); // '{"objects":[],"background":"rgba(0, 0, 0, 0)"}'
JSON.stringify()
method, which calls the toJSON
method on an object if this method exists. The canvas object in Fabric has this method, it is equivalent to calling JSON.stringify(canvas.toJSON())
. canvas.backgroundColor = 'red'; JSON.stringify(canvas); // '{"objects":[],"background":"red"}'
canvas.add(new fabric.Rect({ left: 50, top: 50, height: 20, width: 20, fill: 'green' })); console.log(JSON.stringify(canvas));
'{"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)"}'
canvas.add(new fabric.Circle({ left: 100, top: 100, radius: 50, fill: 'red' })); console.log(JSON.stringify(canvas));
'{"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"
and "type":"circle"
to indicate where these objects begin. It may seem that the string is too long, but it's flowers, compared with the serialization of images. For comparison, let's look at the 1/10 (!) Line canvas.toDataURL('png')
method: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAK8CAYAAAAXo9vkAAAgAElEQVR4Xu3dP4xtBbnG4WPAQOQ2YBCLK1qpoQE1/m+NVlCDwUACicRCEuysrOwkwcJgAglEItRQaWz9HxEaolSKtxCJ0FwMRIj32zqFcjm8e868s2fNWo/Jygl+e397rWetk5xf5pyZd13wPwIECBAgQIAAAQIECBxI4F0H++Qb134R/U2fevC8q+5esGWESBAgAABAgQIEFiOwPL/MC5AlvO0OBMCBAgQIECAAAECJxQQICcE9HYCBAgQIECAAAECBPYXECD7W3klAQIECBAgQIAAAQInFBAgJwT0dgIECBAgQIAAAQIE9hcQIPtbeSUBAgQIECBAgAABAicUECAnBPR2AgQIECBAgAABAgT2FxAg+1t5JQECBAgQIECAAAECJxQQICcE9HYCBAgQIECAAAECBPYXECD7W3klAQIECBAgQIAAAQInFBAgJwTc9+3z49yvmNd+dI7PzPHJOW6Y4wNzXD3HlXNc9pZdb85/vzbHK3P8aY7n5vj1HL+Y43dz417f97O9jgABAgQIECBAgMBSBATIKd2JCY5dWNwyx5fn+PwcV5U/6tXZ99M5fjjHk3Mjd6HifwQIECBAgAABAgQWLSBAirdnouP6WXfvHHfOcU1x9T6rXp4XPTLHA3NTX9jnDV5DgAABAgQIECBA4NACAuSE4hMdl8+Kr83xzTmuO+G61ttfnEXfnuN7c4PfaC21hwABAgQIECBAgMBJBQTIJQpOeFw7b71/jtsvccWh3vbYfNB9c6NfOtQH+hwCBAgQIECAAAECFxMQIMd8No7C4+F5283HfOtZv/ypOYG7hMhZ3wafT4AAAQIECBDYtoAA2fP+H/1Vqwd3f4jf8y1Lfdkunu7xV7OWenucFwECBAgQIEBg3QICZI/7O/Fxx7xs9wf3t36r3D3evciX7L7F7+6rIY8u8uycFAECBAgQIE'
fabric.Canvas#toObject
method was fabric.Canvas#toObject
. It's simple: toObject
returns the same view as toJSON
, only as an object. For example, take a canvas with content in the form of a green rectangle. canvas.toObject()
will output to the console: { "background" : "rgba(0, 0, 0, 0)", "objects" : [ { "angle" : 0, "fill" : "green", "flipX" : false, "flipY" : false, "hasBorders" : true, "hasControls" : true, "hasRotatingPoint" : false, "height" : 20, "left" : 50, "opacity" : 1, "overlayFill" : null, "perPixelTargetFind" : false, "scaleX" : 1, "scaleY" : 1, "selectable" : true, "stroke" : null, "strokeDashArray" : null, "strokeWidth" : 1, "top" : 50, "transparentCorners" : true, "type" : "rect", "width" : 20 } ] }
toJSON
output is nothing more than a translated toObject
line. The toObject
method toObject
interesting and useful in that it is “smart” and “lazy”. What you see in the array is the result of iterating over all the canvas objects, and delegating the toObject
method to toObject
. The “class” fabric.Path
has its own toObject
, which returns an array of '' points ''. And fabric.Image
also has this method; it returns the '' src '' property on images. Following the OOP pattern, each object knows how to serialize itself.toObject
method that you can rewrite or extend its functionality. var rect = new fabric.Rect(); rect.toObject = function() { return { name: 'trololo' }; }; canvas.add(rect); console.log(JSON.stringify(canvas));
'{"objects":[{"name":"trololo"}],"background":"rgba(0, 0, 0, 0)"}'
toObject
functional toObject
additional properties. var rect = new fabric.Rect(); rect.toObject = (function(toObject) { return function() { return fabric.util.object.extend(toObject.call(this), { name: this.name }); }; })(rect.toObject); canvas.add(rect); rect.name = 'trololo'; console.log(JSON.stringify(canvas));
'{"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
method on the object with the additional property '' name ''. It is now present in the result of the method call. It must be remembered that when extending the functionality in this way, the “class” of the object (in this case, fabric.Rect
) must contain the newly added property in the '' stateProperties '' array. Only in this case everything will work correctly.toSVG
method in action: canvas.add(new fabric.Rect({ left: 50, top: 50, height: 20, width: 20, fill: 'green' })); 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
or toObject
, toSVG
, when called on canvas, delegates its logic to each object, and each of them has its own toSVG
method, which is specific to each type of object. If you need to change or extend the toSVG
method, you can do it as well as with the toObject
method.toObject/toJSON
is that you can coordinate with any device capable of SVG rendering (browser, application, printer, camera, etc.). With the toObject / toJSON methods, you must first load the view onto canvas. By the way, about loading on canvas. We can serialize the contents of the canvas into text, and how can we load it back?fabric.Canvas#loadFromJSON
and fabric.Canvas#loadFromDatalessJSON
. For SVG, the fabric.loadSVGFromURL
and fabric.loadSVGFromString
. var canvas = new fabric.Canvas(); canvas.loadFromJSON('{"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)"}');
loadFromDatalessJSON
method? What is its fundamental difference from the loadFromJSON
that we just used? To understand why we need this method, we need to look at a more or less complex path object, for example, this one:JSON.stringify(canvas)
will output the following: '{"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]'...
["c",0,2.67,-0.979,5.253,-2.048,9.079]
in JSON format are the coordinates of one of the curves. And when there are hundreds or thousands of these curves, it is not difficult to guess how huge the string will be.fabric.Canvas#toDatalessJSON
method comes to the rescue. Let's try: 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)"}
toDatalessJSON
method toDatalessJSON
we set the value of "/assets/dragon.svg" in the "sourcePath" property of the path object (dragon shape). Then we called the toDatalessJSON
method, and the whole huge path string was turned into the simple string "/assets/dragon.svg".toDatalessJSON
method allows us to significantly reduce the text representation of the canvas, and replace the large path data with a simple reference to SVG.loadFromDatalessJSON
method, you probably guessed that it allows you to load a canvas from a dataless view (dataless). loadFromDatalessJSON
can load "path" strings (such as "/assets/dragon.svg") and use them as data for path objects. fabric.loadSVGFromString('...', function(objects, options) { var obj = fabric.util.groupSVGElements(objects, options); canvas.add(obj).renderAll(); });
objects
and options
. objects
- an array of objects obtained from SVG - paths, a group of path-objects (for complex objects), images, text, etc. To group all of these objects into one collection and make them look like they were in an SVG document, we use fabric.util.groupSVGElements
for both objects
and options
. In the end, we get a fabric.Path
or fabric.PathGroup
object that we can add to the canvas.fabric.loadSVGFromURL
works in the same way, except that you use the string that contains the URL, not the SVG content. Please note that Fabric will try to get this URL via XMLHttpRequest, so the SVG link must comply with the SOP rules.fabric.Object
, and some “classes” - like fabric.PathGroup
- even have 3-level inheritance.fabric.util.createClass
, which is a simple abstraction of ordinary prototype inheritance in javascript. First, create a simple "class" Point: var Point = fabric.util.createClass({ initialize: function(x, y) { this.x = x || 0; this.y = y || 0; }, toString: function() { return this.x + '/' + this.y; } });
createClass
takes an object and uses its properties as properties of a new “class” object. '' initialize '' is used as a constructor. Therefore, when we initialize Point, we create a new object with the properties '' x '', '' y '' and the method '' toString '': var point = new Point(10, 20); point.x; // 10 point.y; // 20 point.toString(); // "10/20"
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 + ')'; } });
callSuper
method, which calls the method on the parent "class". This means that if we change the Point, the changes will also affect ColoredPoint
. Let's look at an example: var redPoint = new ColoredPoint(15, 33, '#f55'); redPoint.x; // 15 redPoint.y; // 33 redPoint.color; // "#f55" redPoint.toString(); "15/35 (color: #f55)"
LabeledRect
, which will be just a rectangle labeled. When an instance of the “class” is displayed on canvas, the caption will be displayed inside the rectangle. Something similar (circle and text) we have already considered in the chapter 'Groups'. By the way, while working with Fabric you can see that you can create abstractions here using groups as well as “classes”. var LabeledRect = fabric.util.createClass(fabric.Rect, { type: 'labeledRect', initialize: function(options) { options || (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, -this.height/2 + 20); } });
fabric.Rect
to add its display capabilities. Then we define the '' type '' property and give it the value of the 'labeledRect' '. This is done to be consistent with the Fabric architecture, since all the objects there have a '' type '' property (rectangle, circle, path, text, etc.).initialize
) is already familiar to us, in it we call callSuper
, which calls initialize
on the fabric.Rect
. In addition, we give the object an inscription (label) by taking the value from options
.toObject
and _render
. toObject
, as we remember from the serialization chapter, is responsible for representing the object.LabeledRect
has the same properties as a regular rectangle (rect), we expanded the parent method toObject
by simply adding a label to it._render
method, it is responsible for the immediate drawing of the object. It consists of a rectangle display ( callSuper
) and additional text display logic. var labeledRect = new LabeledRect({ width: 100, height: 50, left: 100, top: 100, label: 'test', fill: '#faa' }); canvas.add(labeledRect);
labeledRect.set({ label: 'trololo', fill: '#aaf', rx: 10, ry: 10 });
toObject
, and initialize
: ... initialize: function(options) { options || (options = { }); this.callSuper('initialize', options); // / 100/50 this.set({ width: 100, height: 50 }); this.set('label', options.label || ''); } ... _render: function(ctx) { // ctx.font = this.labelFont; ctx.fillStyle = this.labelFill; ctx.fillText(this.label, -this.width/2, -this.height/2 + 20); } ...
Source: https://habr.com/ru/post/254763/
All Articles