网站地图    收藏   

主页 > canvas引擎 > Fabricjs >

Fabric.js 简介。第1部分。

来源:未知    时间:2022-07-04 00:09 作者:小飞侠 阅读:

[导读] 今天,我想向您介绍Fabric.js——一个强大的 Javascript 库,它让使用 HTML5 画布变得轻而易举。Fabric 为画布提供了一个缺失的对象模型,以及一个 SVG 解析器、交互层和一整套其他不可或缺...

今天,我想向您介绍Fabric.js——一个强大的 Javascript 库,它让使用 HTML5 画布变得轻而易举。Fabric 为画布提供了一个缺失的对象模型,以及一个 SVG 解析器、交互层和一整套其他不可或缺的工具。它是一个完全开源的项目,在 MIT 下获得许可,多年来做出了许多贡献。

Fabric 于 2010 年左右开始,在发现使用原生 canvas API 的痛苦之后。原作者正在为printio.ru创建一个交互式设计编辑器——他的初创公司允许用户设计自己的服装。他们想要的那种交互性在那个时候只存在于 Flash 应用程序中。即使是现在,也很少有人能接近 Fabric 所带来的可能性。

让我们仔细看看!

为什么是Fabric?

如今, Canvas使我们能够在网络上创建一些绝对 令人惊叹的 图形。但它提供的 API令人失望地低级。如果我们只是想在画布上绘制一些基本形状而忘记它们,那是一回事。但是,一旦需要进行任何类型的交互、随时更改图片或绘制更复杂的形状,情况就会发生巨大变化。

Fabric旨在解决这个问题。

原生画布方法只允许我们触发简单的图形命令,盲目地修改整个画布位图。想画一个矩形?使用fillRect(left, top, width, height). 想画一条线?使用moveTo(left, top)和的组合lineTo(x, y)。就好像我们用画笔在画布上画,在上面涂上越来越多的油,几乎没有控制。

Fabric 并没有在如此低的级别上运行,而是在原生方法之上提供了简单但功能强大的对象模型。它负责画布状态和渲染,让我们直接使用“对象”。

让我们看一个演示这种差异的简单示例。假设我们想在画布上的某处绘制一个红色矩形。下面是我们如何使用原生 <canvas> API 来做到这一点。

// 引用画布元素(id="c")
var canvasEl = document.getElementById('c'); 
// 获取要在其上绘制的 2d 上下文(前面提到的“位图”)
var ctx = canvasEl.getContext('2d'); 
// 设置上下文的填充颜色ctx.fillStyle = 'red'; 
// 在 100,100 点创建矩形,尺寸为 
20x20 ctx.fillRect(100, 100, 20, 20);

现在,让我们看看对 Fabric 做同样的事情:

// 在原生 canvas 元素周围创建一个包装器(id="c")
var canvas = new fabric.Canvas('c'); 
// 创建一个矩形对象
var rect = new fabric.Rect({ 
  left: 100, 
  top: 100, 
  fill: 'red', 
  width: 20, 
  height: 20 }); 
// 在画布上“添加”矩形canvas.add(rect);

在这一点上,大小几乎没有区别——这两个例子非常相似。但是,您已经可以看到使用画布的方法有多么不同。使用本机方法,我们对上下文进行操作——一个表示整个画布位图的对象。在 Fabric 中,我们对对象进行操作——实例化它们,更改它们的属性,并将它们添加到画布中。可以看到,这些对象是 Fabric 土地上的一等公民。

但是渲染纯红色矩形实在是太无聊了。我们至少可以用它做一些有趣的事情!也许,稍微旋转一下?

让我们试试45度。首先,使用原生 <canvas> 方法:

var canvasEl = document.getElementById('c'); 
var ctx = canvasEl.getContext('2d'); 
ctx.fillStyle = '红色'; 
ctx.translate(100, 100); 
ctx.rotate(Math.PI / 180 * 45); 
ctx.fillRect(-10, -10, 20, 20);

现在使用织物:

var canvas = new fabric.Canvas('c'); // 创建一个角度为 45 的矩形
var rect = new fabric.Rect({ 
  left: 100, 
  top: 100, 
  fill: 'red', 
  width: 20, 
  height: 20, 
  angle: 45 
  }); 
  // "add" 长方形 在 canvas 上面
  canvas.add(rect);

这里发生了什么?

我们在 Fabric 中要做的就是将对象的“角度”值更改为45. 然而,使用本机方法,事情变得更加“有趣”。请记住,我们不能对对象进行操作。相反,我们调整整个画布位图 ( ctx.translate, ctx.rotate) 的位置和角度以满足我们的需要。然后我们再次绘制矩形,但记住要正确偏移位图(-10,-10),以便它仍然在 100,100 处渲染。作为奖励练习,我们必须在旋转画布位图时将度数转换为弧度。

我相信您已经开始了解 Fabric 存在的确切原因以及它隐藏了多少低级样板。

但是让我们看另一个例子——跟踪画布状态。

如果在某个时候,我们想将现在熟悉的红色矩形移动到画布上稍微不同的位置怎么办?如果不能对对象进行操作,我们将如何做到这一点?fillRect我们会在画布位图上调用另一个吗?

不完全的。调用另一个fillRect命令实际上会在画布上已经绘制的任何内容之上绘制矩形。还记得我之前提到过用画笔绘画吗?为了“移动”它,我们需要先擦除之前绘制的内容,然后在新位置绘制矩形。

var canvasEl = document.getElementById('c');
var ctx = canvasEl.getContext('2d');
ctx.fillStyle = 'red';ctx.translate(100, 100);
ctx.rotate(Math.PI / 180 * 45);
ctx.fillRect(-10, -10, 20, 20);

我们将如何使用 Fabric 实现这一点?

var canvas = new fabric.Canvas('c');

// create a rectangle with angle=45
var rect = new fabric.Rect({
  left: 100,
  top: 100,
  fill: 'red',
  width: 20,
  height: 20,  angle: 45});

canvas.add(rect);

注意一个非常重要的区别。使用 Fabric,我们不再需要在尝试“修改”任何内容之前擦除内容。我们仍然使用对象,只需更改它们的属性,然后重新渲染画布以获得“新鲜图片”。

对象

fabric.Rect我们已经看到了如何通过实例化构造函数来处理矩形。当然,Fabric 也涵盖了所有其他基本形状——圆形、三角形、椭圆形等等。所有这些都暴露在fabric“命名空间”下,如fabric.Circle、fabric.Triangle、fabric.Ellipse等。

Fabric 中提供了 7 种基本形状:

  • 织物.圆

  • 织物.椭圆

  • 面料线

  • 织物.多边形

  • 织物.折线

  • 织物.矩形

  • 织物.三角形

想画一个圆圈?只需创建一个圆形对象,并将其添加到画布。与任何其他基本形状相同:

var circle = new fabric.Circle({
  radius: 20, fill: 'green', left: 100, top: 100
});
var triangle = new fabric.Triangle({
  width: 20, height: 30, fill: 'blue', left: 50, top: 50
});

canvas.add(circle, triangle);

..我们在 100,100 位置绘制了一个绿色圆圈,在 50,50 位置绘制了一个蓝色三角形。

操作对象

创建图形对象——矩形、圆形或其他东西——当然只是开始。在某些时候,我们可能想要修改这些对象。也许某些动作需要触发状态变化,或播放某种动画。或者我们可能想要在某些鼠标交互上更改对象属性(颜色、不透明度、大小、位置)。

Fabric 为我们处理画布渲染和状态管理。我们只需要修改对象本身。

前面的示例演示了set方法以及如何set({ left: 20, top: 50 })从先前位置调用“移动”对象。以类似的方式,我们可以更改对象的任何其他属性。但这些属性是什么?

好吧,正如你所期望的那样,有一些与定位相关的东西—— left,top;尺寸——宽度、高度;渲染——填充、不透明度、描边、描边宽度;缩放和旋转 - scaleX , scaleY , angle ; 甚至那些与翻转有关的东西——flipX、flipY和skewX、skewY

是的,在 Fabric 中创建翻转对象就像将 flip* 属性设置为true.

您可以通过方法读取这些属性中的任何一个get,并通过set. 让我们尝试更改一些红色矩形的属性:

var canvas = new fabric.Canvas('c');
...
canvas.add(rect);

rect.set('fill', 'red');
rect.set({ strokeWidth: 5, stroke: 'rgba(100,200,200,0.5)' });
rect.set('angle', 15).set('flipY', true);

首先,我们将“填充”值设置为“红色”,本质上是使对象变为红色。下一条语句设置“strokeWidth”和“stroke”值,为矩形提供5px的淡绿色描边。最后,我们正在更改“angle”和“flipY”属性。请注意这 3 个语句中的每一个语句如何使用略有不同的语法。

这表明这set是一种通用方法。您可能会经常使用它,因此它应该尽可能方便。

我们已经介绍了 setter,那么 getter 呢?很明显,有通用get方法,但也有一些特定get*的方法。要读取对象的“宽度”值,您可以使用get('width')or getWidth()。获得一个“scaleX”值——get('scaleX')或者getScaleX(),等等。每个“公共”对象属性(“stroke”、“strokeWidth”、“angle”等)都有一个类似getWidth或对应的方法getScaleX

set您可能会注意到,在前面的示例中,对象是使用与我们刚刚在方法中使用的配置哈希相同的配置哈希创建的。那是因为它完全一样。您可以在创建时“配置”对象,也可以在以下情况下使用set方法:

var rect = new fabric.Rect({ width: 10, height: 20, fill: '#f55', opacity: 0.7 });

// or functionally identical

var rect = new fabric.Rect();
rect.set({ width: 10, height: 20, fill: '#f55', opacity: 0.7 });

默认选项

此时,您可能会问——当我们创建一个对象而不传递任何“配置”对象时会发生什么。它还有那些属性吗?

是的当然。Fabric 中的对象始终具有一组默认属性。在创建过程中省略时,它是“赋予”对象的默认属性集。我们可以自己试试看:

var rect = new fabric.Rect(); // notice no options passed in

rect.get('width'); // 0
rect.get('height'); // 0

rect.get('left'); // 0
rect.get('top'); // 0

rect.get('fill'); // rgb(0,0,0)
rect.get('stroke'); // null

rect.get('opacity'); // 1

我们的矩形有一组默认属性。它位于 0,0,为黑色,完全不透明,没有描边和尺寸(宽度和高度为 0)。由于没有尺寸,我们无法在画布上看到它。但是给它任何宽度/高度的正值肯定会在画布的左/上角显示一个黑色矩形。

层次结构和继承

Fabric 对象不仅彼此独立存在。它们形成了非常精确的层次结构。

大多数对象都从根继承fabric.Object。fabric.Object几乎代表了一个二维形状,位于二维画布平面中。它是一个具有left/top 和width/height 属性以及许多其他图形特征的实体。我们在对象上看到的那些属性——填充、描边、角度、不透明度、翻转*等——对于所有继承自fabric.Object.

这种继承允许我们定义方法fabric.Object并在所有子“类”之间共享它们。例如,如果您想getAngleInRadians对所有对象都有方法,您只需在以下位置创建它fabric.Object.prototype:

fabric.Object.prototype.getAngleInRadians = function() {
  return this.get('angle') / 180 * Math.PI;
};

var rect = new fabric.Rect({ angle: 45 });
rect.getAngleInRadians(); // 0.785...

var circle = new fabric.Circle({ angle: 30, radius: 10 });
circle.getAngleInRadians(); // 0.523...

circle instanceof fabric.Circle; // true
circle instanceof fabric.Object; // true

如您所见,方法立即在所有实例上可用。

虽然子“类”继承自fabric.Object,但它们通常还定义自己的方法和属性。例如,fabric.Circle需要具有“半径”属性。而且fabric.Image——我们稍后会看到——需要有getElement/setElement方法来访问/设置图像实例源自的 HTML <img> 元素。
使用原型来获得自定义渲染和行为对于高级项目来说非常常见。

Canvas-画布


现在我们更详细地介绍了对象,让我们回到画布。

如果创建画布对象,您可以在所有 Fabric 示例中看到的第一件事 - new fabric.Canvas('...'). fabric.Canvas 作为 <canvas> 元素的包装器,负责管理特定画布上的所有织物对象。它接受一个元素的 id,并返回一个fabric.Canvas.

我们可以add在它上面放置对象,引用它们,或者删除它们:

var canvas = new fabric.Canvas('c');
var rect = new fabric.Rect();

canvas.add(rect); // add object

canvas.item(0); // reference fabric.Rect added earlier (first object)
canvas.getObjects(); // get all objects on canvas (rect will be first and only)

canvas.remove(rect); // remove previously-added fabric.Rect

虽然管理对象是它的主要目的fabric.Canvas,但它也可以作为配置主机。需要为整个画布设置背景颜色或图像?将所有内容剪辑到某个区域?设置不同的宽度/高度?指定画布是否是交互式的?所有这些选项(和其他选项)都可以fabric.Canvas在创建时或之后设置为:

var canvas = new fabric.Canvas('c', {
  backgroundColor: 'rgb(100,100,200)',
  selectionColor: 'blue',
  selectionLineWidth: 2
  // ...
});

// or

var canvas = new fabric.Canvas('c');
canvas.setBackgroundImage('http://...');
canvas.onFpsUpdate = function(){ /* ... */ };
// ...

互动性

当我们讨论画布元素的主题时,让我们谈谈交互性。Fabric 的一个独特功能——内置于其中——是在我们刚刚看到的所有方便的对象模型之上的一层交互性。

存在对象模型以允许对画布上的对象进行编程访问和操作。但是在外部,在用户级别,有一种方法可以通过鼠标(或触摸,在触摸设备上)来操作这些对象。一旦通过 初始化画布new fabric.Canvas('...'),就可以选择对象、拖动它们、缩放或旋转它们,甚至可以组合在一起进行操作!

 

如果我们希望用户允许在画布上拖动某些东西——比如说图像——我们需要做的就是初始化画布并在上面添加一个对象。无需额外的配置或设置。

为了控制这种交互性,我们可以结合单个对象的“可选择”布尔属性在画布上使用 Fabric 的“选择”布尔属性。

var canvas = new fabric.Canvas('c'); ... canvas.selection = false; // 禁用组选择rect.set('selectable', false); // 使对象不可选择

但是,如果您根本不想要这样的交互层怎么办?如果是这种情况,您可以随时替换fabric.Canvas为fabric.StaticCanvas. 初始化的语法是完全一样的;你只使用StaticCanvas而不是Canvas.

var staticCanvas = new fabric.StaticCanvas('c'); staticCanvas.add( 
  new fabric.Rect({ 
    width: 10, height: 20, 
    left: 100, top: 100, 
    fill: 'yellow', 
    angle: 30 
  }));

这创建了一个“更轻”的画布版本,没有任何事件处理逻辑。请注意,您仍然有一个完整的对象模型可供使用——添加对象、删除或修改它们,以及更改任何画布配置——所有这些仍然有效。只有事件处理消失了。

稍后,当我们讨论自定义构建选项时,您会看到如果StaticCanvas您只需要,您甚至可以创建更轻的 Fabric 版本。如果您在应用程序中需要非交互式图表或带有过滤器的非交互式图像,这可能是一个不错的选择。

图片

说到图片……

在画布上添加矩形和圆形很有趣,但我们为什么不玩一些图像呢?正如你现在想象的那样,Fabric 让这一切变得简单。让我们实例化fabric.Image对象并将其添加到画布:

(html)

<canvas id="c"></canvas>
<img src="my_image.png" id="my-image">

(js)

var canvas = new fabric.Canvas('c');
var imgElement = document.getElementById('my-image');
var imgInstance = new fabric.Image(imgElement, {
  left: 100,
  top: 100,
  angle: 30,
  opacity: 0.85
});
canvas.add(imgInstance);

请注意我们如何将图像元素传递给fabric.Image构造函数。这将创建一个fabric.Image看起来就像文档中的图像的实例。此外,我们立即将 left/top 值设置为 100/100,角度设置为 30,不透明度设置为 0.85。添加到画布后,图像会在 100,100 位置、30 度角处渲染,并且略微透明!不错。

现在,如果我们在文档中没有真正的图像,而只有图像的 URL,该怎么办?不是问题。让我们看看如何使用fabric.Image.fromURL:

fabric.Image.fromURL('my_image.png', function(oImg) { 
  canvas.add(oImg); });

看起来很简单,不是吗?只需fabric.Image.fromURL使用图像的 URL 调用,并在加载和创建图像后给它一个回调以调用。回调函数接收已经创建的fabric.Image对象作为第一个参数。此时,您可以将其添加到画布或先更改,然后添加到画布:

fabric.Image.fromURL('my_image.png', function(oImg) {
  // scale image down, and flip it, before adding it onto canvas
  // 缩放图形比例并翻转它,在这个元素添加到画布之前。
  oImg.scale(0.5).set('flipX', true);
  canvas.add(oImg);
});

路径

我们看了简单的形状,然后是图像。更复杂、更丰富的形状和内容呢?

认识强大的一对——路径和组。

Fabric 中的路径表示可以以其他方式填充、描边和修改的形状轮廓。路径由一系列命令组成,这些命令基本上模仿了一支笔从一个点到另一个点。借助“move”、“line”、“curve”或“arc”等命令,路径可以形成极其复杂的形状。在路径组 (PathGroup's) 的帮助下,可能性更大。

Fabric 中的路径非常类似于SVG <path> 元素。它们使用相同的命令集,可以从 <path> 元素创建,并序列化到它们中。稍后我们将更仔细地研究序列化和 SVG 解析,但现在值得一提的是,您可能很少会手动创建 Path 实例。相反,您将使用 Fabric 的内置 SVG 解析器。但要了解什么是 Path 对象,让我们尝试手动创建一个简单的对象:

var canvas = new fabric.Canvas('c');
var path = new fabric.Path('M 0 0 L 200 100 L 170 200 z');
path.set({ left: 120, top: 120 });
canvas.add(path);

我们是实例化fabric.Path对象,向它传递一串路径指令。虽然看起来很神秘,但实际上很容易理解。“M”代表“移动”命令,告诉隐形笔移动到0、0点。“L”代表“线”,使笔画一条线到200、100点。然后,另一个“L”创建一条到 170、200 的线。最后,“z”告诉强制绘图笔关闭当前路径并最终确定形状。结果,我们得到一个三角形。

因为fabric.Path就像 Fabric 中的任何其他对象一样,我们也能够更改它的一些属性。但我们可以进一步修改它:

...
var path = new fabric.Path('M 0 0 L 300 100 L 200 300 z');
...
path.set({ fill: 'red', stroke: 'green', opacity: 0.5 });
canvas.add(path);

出于好奇,让我们看一下稍微复杂一点的路径语法。你会明白为什么手工创建路径可能不是最好的主意。

...
var path = new fabric.Path('M121.32,0L44.58,0C36.67,0,29.5,3.22,24.31,8.41\
c-5.19,5.19-8.41,12.37-8.41,20.28c0,15.82,12.87,28.69,28.69,28.69c0,0,4.4,\
0,7.48,0C36.66,72.78,8.4,101.04,8.4,101.04C2.98,106.45,0,113.66,0,121.32\
c0,7.66,2.98,14.87,8.4,20.29l0,0c5.42,5.42,12.62,8.4,20.28,8.4c7.66,0,14.87\
-2.98,20.29-8.4c0,0,28.26-28.25,43.66-43.66c0,3.08,0,7.48,0,7.48c0,15.82,\
12.87,28.69,28.69,28.69c7.66,0,14.87-2.99,20.29-8.4c5.42-5.42,8.4-12.62,8.4\
-20.28l0-76.74c0-7.66-2.98-14.87-8.4-20.29C136.19,2.98,128.98,0,121.32,0z');

canvas.add(path.set({ left: 100, top: 200 }));

哦,男孩,这是怎么回事?

好吧,“M”仍然代表“移动”命令,所以笔从“121.32, 0”点开始绘制旅程。然后是“L”命令将它带到“44.58, 0”。到目前为止,一切都很好。下一步是什么?“C”命令,代表“三次贝塞尔曲线”。它使笔从当前点绘制贝塞尔曲线到“36.67,0”之一。它使用“29.5, 3.22”作为行首的控制点,“24.31, 8.41”作为行尾的控制点。整个过程之后是十几个其他三次贝塞尔命令,最终创建了一个漂亮的箭头形状。

很有可能,您不会直接与这样的“野兽”一起工作。相反,您可能希望使用类似fabric.loadSVGFromString或fabric.loadSVGFromURL方法来加载整个 SVG 文件,并让 Fabric 的 SVG 解析器完成遍历所有 SVG 元素并创建相应 Path 对象的工作。

说到整个 SVG 文档,虽然 Fabric 的路径通常表示 SVG <path> 元素,但通常存在于 SVG 文档中的路径集合表示为组(fabric.Group实例)。可以想象,Group 只不过是一组 Paths 和其他对象。并且由于fabric.Group继承自fabric.Object,它可以像任何其他对象一样添加到画布中,并以相同的方式进行操作。

就像使用路径一样,您可能不会直接使用它们。但是,如果您在解析 SVG 文档后偶然发现了一个,您就会确切地知道它是什么以及它的用途。

后记

我们只触及了 Fabric 所能实现的表面。您现在可以轻松创建任何简单的形状、复杂的形状、图像;将它们添加到画布上,并以任何你想要的方式进行修改——位置、尺寸、角度、颜色、笔触、不透明度——你可以命名它。

在本系列的下一部分中,我们将了解如何与组一起工作;动画; 文本; SVG解析、渲染、序列化;事件;图像过滤器;和更多。

同时,随意查看带注释的演示或基准,加入google group或其他地方的讨论,或直接访问docs、wiki和source。

尝试使用 Fabric 玩得开心!我希望你喜欢这个旅程。

阅读第 2 部分。


自学PHP网专注网站建设学习,PHP程序学习,平面设计学习,以及操作系统学习

京ICP备14009008号-1@版权所有www.zixuephp.com

网站声明:本站所有视频,教程都由网友上传,站长收集和分享给大家学习使用,如由牵扯版权问题请联系站长邮箱904561283@qq.com

添加评论