fabric.js 中文教程第一部分

引言

Fabric,一个功能强大的Javascript库,让我们轻而易举的使用HTML5 Canvas,Fabric为Canvas提供了对象模型,事件交互,SVG解析等功能,以及一整套其他必不可少的工具。这是一个完全开源的项目,MIT许可,多年来做出了许多贡献。

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

Canvas介绍

<canvas>是一个可以使用脚本 (通常为JavaScript) 来绘制图形的 HTML 元素。例如,它可以用于绘制图表、制作图片构图或者制作简单的动画。

一个简单的例子

让我们来看个简单的例子,我们绘制了两个长方形,其中的一个有着alpha透明度。

<html>
<head>
    <script type="application/javascript">
      function draw() {
        var canvas = document.getElementById("canvas");
        if (canvas.getContext) {
          var ctx = canvas.getContext("2d");

          ctx.fillStyle = "rgb(200,0,0)";
          ctx.fillRect (10, 10, 55, 50);

          ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
          ctx.fillRect (30, 30, 55, 50);
        }
      }
    </script>
</head>
<body onload="draw();">
<canvas id="canvas" width="150" height="150"></canvas>
</body>
</html>

例子看起来像是这样:
绘制了两个矩形

Canvas坐标系

grid
Canvas坐标系以左上角为原点,所有元素的位置都相对于原点定位。

参考文章

MDN Canvas教程

基础概念

什么是fabric

Canvas可以让我们在网络上创造出绝对惊人的图形。但它提供的API是令人失望的。如果我们只想在画布上画几条基本的形状,不会觉得有什么繁琐。但是一旦需要任何形式的交互,在任意一点改变画面或绘制更复杂的形状,代码复杂度会急剧增加。

Fabric旨在解决这个问题。

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

Fabric不是在这么低的层次上运行,而是在原生方法之上提供简单而强大的对象模型。它负责画布状态和渲染,并让我们直接使用绘制后的“对象”。

让我们来看一个简单的例子来说明这个差异。假设我们想在画布上画一个红色的矩形。以下是我们如何使用原生的<canvas>API。

// 有一个id是c的canvas元素
var canvasEl = document.getElementById('c');

// 获取2d位图模型
var ctx = canvasEl.getContext('2d');

// 设置填充颜色
ctx.fillStyle = 'red';

// 创建一个坐标100,190,尺寸是20,20的矩形
ctx.fillRect(100, 100, 20, 20);

现在使用Fabric做同样的事情:

// 用原生canvas元素创建一个fabric实例
var canvas = new fabric.Canvas('c');

// 创建一个矩形对象
var rect = new fabric.Rect({
  left: 100,
  top: 100,
  fill: 'red',
  width: 20,
  height: 20
});

// 将矩形添加到canvas画布上
canvas.add(rect);

矩形
在这种情况下,这两个例子非常相似,绘制出来的效果几乎没有差别。但是,您可以看到使用canvas的方法有多么不同。使用原生方法,我们在上下文中操作(表示整个画布位图的对象),在Fabric中,我们操作对象,实例化它们,更改其属性,并将其添加到画布。你可以看到这些对象是Fabric中的一等公民

但渲染纯正的红色矩形就如此无聊。我们至少可以做一些有趣的事情!也许,稍稍旋转?

旋转45度,首先使用原生的canvas方法:

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');

// 创建一个45度的矩形
var rect = new fabric.Rect({
  left: 100,
  top: 100,
  fill: 'red',
  width: 20,
  height: 20,
  angle: 45
});

canvas.add(rect);

矩形旋转
这里发生了什么?

我们在Fabric中所做的一切都是将对象的“角度”值更改为45。然而使用原生的方法,事情变得更加棘手,记住我们无法对对象进行操作,相反,我们调整整个画布位图(ctx.translatectx.rotate)的位置和角度,以适应我们的需要。然后,我们再次绘制矩形,但记住要正确地偏移位图(-10,-10),所以它仍然呈现在100,100点。作为练习,我们不得不在旋转画布位图时将度数转换为弧度。

我相信你刚刚开始明白为什么Fabric存在,以及它解决了多少低级写法。

如果在某些时候,我们想将现在熟悉的红色矩形移动到画布上稍微不同的位置怎么办?我们如何在无法操作对象的情况下执行此操作?我们会在canvas位图上调用另一个fillRect吗?

不完全是。调用另一个fillRect命令实际上在画布上绘制所有的东西之上绘制矩形。还记得我前边说的用刷子画画吗?为了“移动”它,我们需要先擦除以前绘制的内容,然后在新的位置绘制矩形。

var canvasEl = document.getElementById('c');

...
ctx.strokRect(100, 100, 20, 20);
...

// 擦除整个画布
ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
ctx.fillRect(20, 50, 20, 20);

我们如何用Fabric完成这个?

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

rect.set({ left: 20, top: 50 });
canvas.renderAll();

移动图片位置
注意一个非常重要的区别。使用Fabric,在尝试“修改”任何内容之前,我们不再需要擦除内容。我们仍然使用对象,只需更改其属性,然后重新绘制画布即可获得“最新图片”。

对象

我们已经看到如何通过实例化fabric.Rect构造函数来处理矩形。当然,Fabric也涵盖了所有其他的基本形状:圆,三角形,椭圆等。所有的这些在就是Fabric“命名空间”下的:fabric.Circlefabric.Trianglefabric.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})来移动对象。以类似的方式,我们可以改变对象的任何其他属性。但这些属性是什么?

那么,正如你所期望的那样,可以改变定位:topleft。尺寸:widthheight。渲染:fill,opacity,stroke,strokeWidth。缩放和旋转:scaleX,scaleY,angle;甚至可以翻转:flipXflipY。歪斜:skewXskewY

是的,在Fabric中翻转对象非常简单,将flip[X|Y]设置为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);

红色矩形
首先,我们将fill值设置为red,使对象成为红色。下一个语句设置strokeWidthstroke值,给出矩形为淡绿色的5px笔画。最后,我们正在改变angleflipY的属性。注意每个3个语句中的每个语句使用的语法略有不同。

这表明set是一种通用的方法。你可能经常使用它,所以它被设计得尽可能的方便。

说了如何set的方法,那么如何获取呢?这就有了与之对应的get方法,还有一些具体的get*,要读取对象的“width”值,可以使用get('width')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 });

// 或者这样

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

默认设置

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

当然是。 Fabric 中的对象总是具有一组默认的属性值。 在创建过程中参数未被传递时会被设置成默认的参数。我们可以自己试试看看:

var rect = new fabric.Rect(); // 注意没有传递参数

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几乎代表二维形状,位于二维canvas平面,它是一个具有left / top和width / height属性的实体,以及一些其他图形特征。我们在物体上看到的那些属性:fill,stroke,angle,opacity,flip[X|Y]等,对于从fabric.Object继承的所有Fabric对象都是通用的。

这个继承允许我们在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

您可以看到,方法立即在所有实例上可用。

虽然子类“class”继承自fabric.Object,但它们通常也定义自己的方法和属性。例如,fabric.Circle需要有“radius”属性。而fabric.Image(我们稍后会看),需要使用getElement/setElement方法来访问/设置图像实例的真实HTML<img>元素。

使用原型来获取自定义渲染和行为对于高级项目来说是非常普遍的。

Canvas

我们已经详细地介绍了对象,让我们回到画布上。

您可以在所有Fabric示例中看到的第一件事是创建canvas对象-new Fabric.canvas(“…”)。fabric.Canvas作为<Canvas>元素的包装器,负责管理该特定画布上的所有fabric对象。它获取元素的id,并返回fabric.Canvas的实例。

我们可以在其中添加对象,从中引用对象,或删除对象:

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

canvas.add(rect); // 添加对象

canvas.item(0); // 引用rect
canvas.getObjects(); // 获取画布上的所有对象(rect将是第一个也是唯一一个)

canvas.remove(rect); // 移除之前添加的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的一个独特特性——它是内置的——是在我们刚刚看到的所有方便的对象模型之上的一层交互性。

对象模型的存在允许通过编程的方式来访问和操纵画布上的对象。但在外部,在用户层面,有一种方法可以通过鼠标(或触摸设备上的触摸)操纵这些对象。只要您通过新的fabric.canvas(“…”)初始化canvas,就可以选择对象,拖动它们,缩放或旋转它们,甚至可以组合在一起在一个块中进行操作!

选中操作
组合操作
如果我们希望用户允许在画布上拖拽一些东西,比如说一个图像,我们只需要初始化画布并在上面添加一个对象。不需要其他配置或设置。

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

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

但如果你根本不想要这样的互动层呢?如果是这样的话,你可以随时用fabric.StaticCanvas代替fabric.Canvas。初始化的语法完全相同;只使用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对象并将其添加到画布:

<canvas id="c"></canvas>
<img src="my_image.png" id="my-image">
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度角渲染,并且略微透明!不错。
绘制图片
现在,如果我们在html document中没有真正的图像,而只有图像的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) {
  // 在添加到画布之前,缩小图像, 并使其翻转。
  oImg.scale(0.5).set('flipX', true);
  canvas.add(oImg);
});

路径

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

更强大的工具——路径和组。

Fabric 中的路径表示可以用其他方式填充、描边和修改的形状的轮廓。路径由一系列命令组成,基本上模拟笔从一个点到另一个点。在move(移动)、line(线段)、curve(曲线)或arc(圆弧)等命令的帮助下,路径可以形成极其复杂的形状。在路径组(PathGroup)的帮助下,可能性更大。

要了解Path对象是什么,让我们尝试手动创建一个简单的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);

使用Path绘制的三角形

如果您熟悉SVG,就会发现,Fabric 中的路径与SVG<path>元素非常相似。它们使用相同的命令集,您可以使用<path>元素创建路径,然后序列化到画布中,稍后我们将更仔细地研究序列化和SVG解析。但现在值得一提的是,您可能很少手工创建Path实例,因为使用Fabric的内置SVG解析器将更加方便。

回到上面的例子,我们实例化了一个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.loadSVGFromStringfabric.loadSVGFromURL方法来加载整个SVG文件,让Fabric的SVG解析器完成遍历所有SVG元素并创建相应的Path对象的工作。

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

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

总结

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

在本系列的下一部分中,我们将了解如何使用Group;动画文本SVG解析、渲染、序列化;事件;图像过滤器;等等。

同时,请随时查看带注释的演示基准测试,加入谷歌小组其他地方的讨论,或直接查看文档wiki源代码

享受Fabric实验的乐趣!我希望你旅途愉快。

为方便技术交流,创建了个QQ群:586502738

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值