相关文章推荐
刚毅的皮带  ·  TDengine 入门教程 ...·  7 月前    · 
腹黑的企鹅  ·  Installing the NVIDIA ...·  9 月前    · 
重感情的冲锋衣  ·  Android开发 ...·  1 年前    · 
一身肌肉的镜子  ·  PHP ...·  1 年前    · 

Fabric.js 简介。 第 3 部分。

我们已经在本系列的第一 部分和 第二 部分中 介绍了大部分基础知识。 让我们继续更高级的东西!

团体

我们首先要讨论的是组。 组是 Fabric 最强大的功能之一。 它们正是它们听起来的样子——一种将任何 Fabric 对象组合成单个实体的简单方法。 我们为什么要这样做? 当然,要能够将这些对象作为一个单元来工作!

还记得如何用鼠标将画布上的任意数量的 Fabric 对象分组,形成一个单一的选择吗? 分组后,所有对象都可以移动,甚至可以一起修改。 他们组成一个群体。 我们可以缩放、旋转,甚至改变它的表现属性——颜色、透明度、边框等。

这正是组的用途,每次您在画布上看到这样的选择时,Fabric 都会在幕后隐式创建一组对象。 只有以编程方式提供与组合作的访问权限才有意义。 这是 fabric.Group 为了什么。

让我们创建一组 2 个对象,圆形和文本:

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

首先,我们创建了一个“hello world”文本对象。 Setting originX originY to 'center' 将使它在组内居中; 默认情况下,组成员的方向相对于组的左上角。 然后,半径为 100px 的圆,填充“#eef”颜色并垂直挤压(scaleY=0.5)。 然后我们创建了一个 fabric.Group 实例,将这两个对象传递给它的数组,并给它 150/100 的位置和 -10 的角度。 最后,该组被添加到画布中,就像任何其他对象一样(带有 canvas.add() )。

瞧! 您会在画布上看到一个对象,它看起来像一个带标签的椭圆。 请注意,为了修改该对象,我们只需更改组的属性,为其提供自定义左侧、顶部和角度值。 您现在可以将此对象作为单个实体使用。

现在我们在画布上有一个组,让我们稍微改变一下:

// 为了使用 setFill 命名的 setter,您需要添加可选的命名 setter/getter // 来自 src/util/named_accessors.mixins.js 的代码group.item(0).set('fill', 'red') ; group.item(1).set({ 
  text: 'trololo', 
  fill: 'white' });

这里发生了什么? 我们通过 item() 方法访问组中的单个对象,并修改它们的属性。 第一个对象是压缩的圆圈,第二个是文本。 让我们看看发生了什么:

您现在可能已经注意到的一件重要的事情是,组中的对象都相对于组的中心定位。 当我们更改文本对象的文本时,即使更改了它的宽度,它也保持居中。 如果您不想要这种行为,您需要指定对象的左/上坐标。 在这种情况下,它们将根据这些坐标组合在一起。

让我们创建并分组 3 个圆圈,使它们一个接一个地水平放置:

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

使用组时要记住的另一件事是 对象的状态 例如,当用图像组成一个组时,您需要确保这些图像已完全加载。 由于 Fabric 已经提供了确保加载图像的辅助方法,因此这变得相当容易:

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' , 函数(img){ 
      var img3 = img.scale(0.1).set({ left: 250, top: 250 }); 
      canvas.add(new fabric.Group([ img1, img2, img3], { left: 200 , 顶部: 200 })) 
  }); });

那么在与组合作时还有哪些其他方法可用? 有一个方法,它的工作原理 与返回一个组中所有对象的数组 getObjects() 完全相同。 fabric.Canvas#getObjects() 代表 size() 一个组中所有对象的数量。 contains() 哪些允许检查特定对象是否在组中。 item() ,我们之前看到的,允许检索组中的特定对象。 forEachObject() 再次,镜像 fabric.Canvas#forEachObject ,仅与组对象有关 最后,还有相应的 add() 方法 remove() 可以在组中添加和删除对象。

您可以通过 2 种方式从组中添加/删除对象 - 更新组尺寸/位置和不更新。 我们建议使用更新尺寸,除非您正在执行批量操作并且在此过程中您对组的宽度/高度错误没有任何问题

在组的中心添加矩形:

group.add(new fabric.Rect({ 
  originX: 'center', 
  originY: 'center' }));

要添加距离组中心 100px 的矩形:

group.add(new fabric.Rect({ 
  left: 100, 
  top: 100, 
  originX: 'center', 
  originY: 'center' }));

要在组的中心添加矩形并更新组的尺寸:

group.addWithUpdate(new fabric.Rect({ 
  left: group.get('left'), 
  top: group.get('top'), 
  originX: 'center', 
  originY: 'center' }));

要在距离组中心 100 像素处添加矩形并更新组的尺寸:

group.addWithUpdate(new fabric.Rect({ 
  left: group.get('left') + 100, 
  top: group.get('top') + 100, 
  originX: 'center', 
  originY: 'center' }));

最后,如果你想用画布上已经存在的对象创建一个组,你需要先克隆它们:

// 创建一个包含现有 (2) 个对象副本的组var group = new fabric.Group([ 
  canvas.item(0).clone(), 
  canvas.item(1).clone() ]); // 移除所有对象并重新渲染canvas.clear().renderAll(); // 添加组到画布canvas.add(group);

序列化

一旦您开始构建某种有状态的应用程序,可能允许用户将画布内容的结果保存在服务器上,或者将内容流式传输到不同的客户端,您就需要 画布序列化 你还怎么发送画布内容? 当然,总是可以选择将画布导出到图像,但是将图像上传到服务器肯定会占用大量带宽。 在大小方面没有什么比文本更好的了,这正是 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 传递对象的方法。 由于 Fabric 中的 canvas 实例有 toJSON 方法,就好像我们被调用了一样 JSON.stringify(canvas.toJSON())

注意表示空画布的返回字符串。 它采用 JSON 格式,基本上由“对象”和“背景”属性组成。 “对象”当前为空,因为画布上没有任何内容,并且背景具有默认的透明值(“rgba(0, 0, 0, 0)”)。

让我们给画布不同的背景,看看事情是如何变化的:

canvas.backgroundColor = '红色'; JSON.stringify(画布); // '{"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":假,"不透明度":1,"可选择":真,"hasControls":真,"hasBorders":真,"hasRotatingPoint":假,"透明角":真,"perPixelTargetFind":假,"rx":0, "ry":0}],"背景":"rgba(0, 0, 0, 0)"}'

哇。 乍一看,变化很大,但仔细观察,我们发现它是新添加的对象,现在是“对象”数组的一部分,序列化为 JSON。 请注意,它的表示如何包括其所有的视觉特征——左、上、宽、高、填充、描边等等。

如果我们要添加另一个对象——比如说,一个位于矩形旁边的红色圆圈,你会看到表示相应地发生了变化:

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":假,"不透明度":1,"可选择":真,"hasControls":真,"hasBorders":真,"hasRotatingPoint":假,"透明角":真,"perPixelTargetFind":假,"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}],"背景":"rgba(0, 0, 0, 0)"}'hasRotatingPoint":false,"transparentCorners":true,"perPixelTargetFind":false,"radius":50}],"background":"rgba(0, 0, 0, 0)"}'hasRotatingPoint":false,"transparentCorners":true,"perPixelTargetFind":false,"radius":50}],"background":"rgba(0, 0, 0, 0)"}'

我突出显示 "type":"rect" "type":"circle" 部分,以便您可以更好地看到这些对象的位置。 尽管一开始看起来有很多输出,但 与图像序列化所得到的相比,这根本算不上什么 只是为了比较,让我们看一下你会得到的字符串的大约 1/10(!) canvas.toDataURL('png')

数据:图像/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAK8CAYAAAAXo9vkAAAgAElEQVR4Xu3dP4xtBbnG4WPAQOQ2YBCLK1qpoQE1/m+NVlCDwUACicRCEuysrOwkwcJgAglEItRQaWz9HxEaolSKtxCJ0FwMRIj32zqFcjm8e868s2fNWo/Jygl+e397rWetk5xf5pyZd13wPwIECBAgQIAAAQIECBxI4F0H+AgQIECBAgAABAgLEM0CAAAECBAgQIECAwMEEBMjBqH0QAQIECBAgQIAAAQICxDNAgAABAgQIECBAgMDBBATIwah9EAECBAgQIECAAAECAsQzQIAAAQIECBAgQIDAwQQEyMGofRABAgQIECBAgAABAgLEM0CAAAECBAgQIECAwMEEBMjBqH0QAQIECBAgQIAAAQICxDNAgAABAgQIECBAgMDBBATIwah9EAECBAgQIECAAAECyw+Qb134R/U2fevC8q+5esGWESBAgAABAgQIEFiOwPL/MC5AlvO0OBMCBAgQIECAAAECJxQQICcE9HYCBAgQIECAAAECBPYXECD7W3klAQIECBAgQIAAAQInFBAgJwT0dgIECBAgQIAAAQIE9hcQIPtbeSUBAgQIECBAgAABAicUECAnBPR2AgQIECBAgAABAgT2FxAg+1t5JQECBAgQIECAAAECJxQQICcE9HYCBAgQIECAAAECBPYXECD7W3klAQIECBAgQIAAAQInFBAgJwTc9+3z49yvmNd+dI7PzPHJOW6Y4wNzXD3HlXNc9pZdb85/vzbHK3P8aY7n5vj1HL+Y43dz417f97O9jgABAgQIECBAgMBSBATIKd2JCY5dWNwyx5fn+PwcV5U/6tXZ99M5fjjHk3Mjd6HifwQIECBAgAABAgQWLSBAirdnouP6WXfvHHfOcU1x9T6rXp4XPTLHA3NTX9jnDV5DgAABAgQIECBA4NACAuSE4hMdl8+Kr83xzTmuO+G61ttfnEXfnuN7c4PfaC21hwABAgQIECBAgMBJBQTIJQpOeFw7b71/jtsvccWh3vbYfNB9c6NfOtQH+hwCBAgQIECAAAECFxMQIMd8No7C4+F5283HfOtZv/ypOYG7hMhZ3wafT4AAAQIECBDYtoAA2fP+H/1Vqwd3f4jf8y1Lfdkunu7xV7OWenucFwECBAgQIEBg3QICZI/7O/Fxx7xs9wf3t36r3D3evciX7L7F7+6rIY8u8uyc

...还有 17000 个字符

您可能想知道为什么还有 fabric.Canvas#toObject . 很简单, toObject 返回与 相同的表示 toJSON ,只是以实际对象的形式,没有字符串序列化。 例如,以前面的只有一个绿色矩形的画布为例,`canvas.toObject()` 的输出是这样的:

{“背景”:“rgba(0,0,0,0)”,
  “对象”:[ 
      “角度”:0,
      “填充”:“绿色”,
      “flipX”:假,
      “flipY”:假,
      “hasBorders”:true,
      “hasControls”:true,
      “hasRotatingPoint”:false,
      “height”:20,
      “left”:50,
      “opacity”:1,
      “overlayFill”:null,
      “perPixelTargetFind”:false,
      “scaleX " : 1, 
      "scaleY" : 1, 
      "selectable" : true, 
      "stroke" :空, 
      “strokeDashArray”:空,
      “strokeWidth”:1,
      “top”:50,
      “transparentCorners”:true,
      “类型”:“矩形”,
      “宽度”:20 
  ] }

如您所见, toJSON 输出本质上是一个字符串化的 toObject 输出。 现在,有趣(而且有用!)的事情是 toObject 输出既聪明又懒惰。 您在“对象”数组中看到的是遍历所有画布对象并委托给它们自己的 toObject 方法的结果。 fabric.Path 有自己的 toObject ——知道返回路径的“points”数组,并且 fabric.Image 有自己的 toObject ——知道返回图像的“src”属性。 在真正面向对象的方式中,所有对象都能够序列化自己。

这意味着当您创建自己的“类”,或者只需要自定义对象的序列化表示时,您需要做的就是使用 toObject 方法——要么完全替换它,要么扩展它。 让我们试试这个:

var rect = new fabric.Rect(); rect.toObject = function() { 
  return { name: 'trololo' }; }; 画布.添加(矩形);console.log(JSON.stringify(canvas));

..记录的输出是:

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

如您所见,objects 数组现在具有我们矩形的自定义表示。 这种覆盖可能不是很有用——尽管提出了要点——所以我们不如 用额外的属性来 扩展 矩形的方法。 toObject

var rect = new fabric.Rect(); rect.toObject = (function(toObject) { 
  return function() { 
    return fabric.util.object.extend(toObject.call(this), { 
      name: this.name