:-: **介绍一**
[TOC]
![来自http://fabricjs.com](https://github.com/kangax/fabric.js/raw/master/lib/screenshot.png =400x400)
`Fabric.js`是一款可以更简便的操作`HTML5 canvas`画布的强大的`javascript`库;`Fabric`提供了对象模型,`svg`解析器,交互层以及一整套完整的工具组件.基于`MIT`协议完全开源,每年都有很多开发者参与贡献.
### Why?
首先,`Canvas`现在允许我们创造强大的图形界面.但是`API`可能比较差强人意.我们很容易忘记它们,即便是要画一些基本的形状,更何况各种交互,更换图片或者更复杂的形状.
`Fabric`的目的就是解决这些问题.
原生的`canvas`方法只允许我们触发简单的图形命令,然后盲目地修改整个画布;
取代了如此`low`的方法,`Fabric`在原生基础上提供了简单且强大的对象模型.它负责画布的状态和渲染,而我们直接面向对象.
下面是一些示例:
> 使用canvas API
```js
// reference canvas element (with id="c")
var canvasEl = document.getElementById('c');
// get 2d context to draw on (the "bitmap" mentioned earlier)
var ctx = canvasEl.getContext('2d');
// set fill color of context
ctx.fillStyle = 'red';
// create rectangle at a 100,100 point, with 20x20 dimensions
ctx.fillRect(100, 100, 20, 20);
```
> 使用`Fabric.js`
```js
// create a wrapper around native canvas element (with id="c")
var canvas = new fabric.Canvas('c');
// create a rectangle object
var rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'red',
width: 20,
height: 20
});
// "add" rectangle onto canvas
canvas.add(rect);
```
> 效果图
![](http://fabricjs.com/article_assets/1.png =300x300)
在这点上,从代码量上看几乎没有差别-上述两个例子基本相同。但是你已经发现了在使用`canvas`上的差别了。原生方法中,我们操作代表整个`canvas`位图的对象`context`,而在`fabric`中我们操作对象去实例化它们,改变它们的属性,然后将它们添加到`canvas`中去。由此可见,`fabric`是面向对象的。
上述渲染一个红色的矩形略显无聊,下面我们来进行一些有趣的操作,比方说轻微的旋转一下?
> 先看原生的方法
```js
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`
```js
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);
```
![](http://fabricjs.com/article_assets/2.png =300x300)
我们在`fabric`中只需要改变`angle`到45度即可,使用原生的方法,事情反而不是那么有趣了。首先我们得记住不能直接操作对象,我们通过操作`canvas`的上下文对象来改变位置,调整角度以满足我们的需求。接着我们画矩形,记住偏移位置,让其可以在准确的点渲染出来。额外地,我们还得将旋转度数转为弧度。
下面我们看下其他的例子,如何跟踪记录`canvas`的状态。
如果现在我们要将熟悉的红色矩形移动到`canvas`上的另外一个位置,该怎么办呢?我们如何不操作对象来实现呢?我们是在画布上再调用一个`fillRect`方法吗?
不完全正确!重新调用另一个`fillRect`命令其实是在原先的矩形上再绘制一个矩形。就像画画一样,为了移动它,我们需要先擦掉之前画的矩形,再在一个新的位置画一个矩形。
```js
var canvasEl = document.getElementById('c');
...
ctx.strokRect(100, 100, 20, 20);
...
// erase entire canvas area
ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
ctx.fillRect(20, 50, 20, 20);
```
> `fabric`怎么做的呢?
```js
var canvas = new fabric.Canvas('c');
...
canvas.add(rect);
...
rect.set({ left: 20, top: 50 });
canvas.renderAll();
```
注意到这个变化,在使用`fabric`的时候我们不再需要在我们尝试修改任何内容的时候,先擦拭掉内容。我们仍然只需要改变对象的属性达到刷新视图的目的。
### Objects
我们已经知道了如何实例化`fabric.Rect`构造函数来画矩形了。`fabric`自然也提供了其他基础形状的构造器了--圆,三角,椭圆等等。它们都是通过`fabric`下的命名空间导出的,例如`fabric.Circle`,`fabric.Triangle`,`fabric.Ellipse`。
`Fabric`提供了7种基本图形的接口
* `farbic.Circle`
* `fabric.Ellipse`
* `fabric.Line`
* `farbic.Polygon`
* `fabric.Polyline`
* `fabric.Rect`
* `fabric.Triangle`
下面给些示例:
```js
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);
```
![](http://fabricjs.com/article_assets/4.png =300x300)
#### 对象操作
以上还仅仅只是个开始,事实上,我们更希望能够修改这些对象。可能是一些状态改变的响应,或者是一些不同的动画。或者我们想在某些鼠标事件下改变对象的属性(颜色,透明度,尺寸,位置)。
`Fabric`帮助我们解决了画布的绘制和状态管理,我们仅仅需要操作这些对象即可。
在此之前,我们使用`set`方法证明了,如何通过方法`set({left:20,top:50})`来移动一个对象。类似的,我们还可以改变对象的其他属性。那些属性呢?
显而易见,你也可以猜到,定位属性——`top,left`,尺寸属性——`width,height`,绘制属性——`fill,opacity,stroke,strokeWidth`,缩放和旋转属性——`scaleX,scaleY,angle`,还有翻转属性`flipX,flipY`,弯曲属性——`skewX,skewY`。
是的,创建翻转元素在`Fabric`中就是如此容易,只需要设置`flip*`属性为`true`。你可以通过`set`来设置这些属性,通过`get`来获取这些属性。下面看个栗子:
```js
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);
```
![](http://fabricjs.com/article_assets/5.png)
分析下上面的代码,首先我们通过设置`fill`的值为`red`,就是将对象填充为红色。然后设置`strokeWidth和stroke`的值,得到了一个5px的绿色外边框。最后我们改变了`angle`和`flipY`的值。
但请注意上面三行代码之间细微的语法差别。
`set`方法是我们常用的方法,这样定义是非常方便使用的。
与之对应,`get`方法也是获取属性值最方便的方法了。例如`get('width')或getWidth()`。
当然我们也可以之间定义个全量的对象,赋值为`set`方法
```js
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`对象都设有默认属性。当创建对象过程中发生遗漏,就会有默认的属性赋值给对象。我们自己可以尝试一下:
```js
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`对象并不是互相独立存在的。它们之间形成了非常明确的层次关系。
绝大多数对象都是继承自根对象`fabricj.Object`。`fabirc.Object`代表一个二维的形状,呈现在二维的`canvas`的画布上。它是一个具有宽高,偏移还有一堆其他图形属性的实体。我们在上面见到的对象的这些属性都是继承来自`fabric.Object`。
这中继承关系,允许我们在`fabirc.Object`上定义方法,共享与`fabric`所有的类。举个栗子,假如你希望所有的对象都有一个`getAngleInRadians`方法,你只需要在`fabric.Object.prototype`的原型上创建即可。
```js
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`需要有半径(`radius`)属性,`fabric.Image`需要`setElement/getElement`方法来讲设置源自`HTML img`标签中的图片。
使用原型获取自定义渲染和行为对于牛逼项目是非常普遍的。
### 画布`Canvas`
我们已经了解了对象的详细信息,现在让我们回归`canvas`.
在所有`fabric`的栗子中,创建对象之前你首先看到的是`new fabric.Canvas('...')`,`fabric.Canvas`给`canvas`元素提供一个包裹容器,它的主要职责就是管理指定画布上的`fabric`对象.它需要元素的`id`返回`fabric.Canvas`的一个实例.
我们可以向里面添加对象,从中引用他们.甚至删除他们:
```js
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
```
尽管管理对象是`farbric.Canvas`的唯一宗旨,但是它还可以当作主要配置项.需要在整个`canvas`上设置一个背景色或图片?将所有内容剪辑到指定区域?设置不同的宽高?指定画布是不是可交互的?所有的这些选项(还有其余的)都可以设置在`fabric.Canvas`上,可以在创建对象时,也可以在此之后:
```js
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(){ /* ... */ };
```
#### 交互
既然我们讨论到了`canvas`元素了,那就让我们讲讲交互吧.`Fabricj`中一个特性就是我们看到的,在对象模型上有一个互动层.
对象模型允许对画布对象进行编程访问和操作.在外面,在用户级别上,可以通过鼠标(触摸,触摸设备)来操作对象.一旦你通过`new fabric.Canvas(..)`初始化画布之后,你就可能选择对象,来回拖拽,旋转,缩放他们,设置将它们分到一个操作组.
![](http://fabricjs.com/article_assets/8.png)
如果我们想要允许用户拖拽画布上的东西,比如说图片,我们需要做的仅仅是初始化画布,然后将对象添加到画布上.不许其他额外的配置和安装.
但是如果我们不想要这样的交互呢?那这样的话,你可以使用`fabric.StaticCanvas`替代`fabric.Canvas`.
初始化语法完全相同,你只需要使用`StaticCanvas`替换`Cavnas`.
```js
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`版本。如果您需要诸如非交互式图表之类的东西,或者在应用程序中使用滤镜的非交互式图像,这可能是一个不错的选择。
### 图片`Images`
说到图片..........
在画布上添加矩形和圆很有趣,但为什么我们没有用到一些图片呢?如你所想,`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度,轻微的透明度.不错!
![](http://fabricjs.com/article_assets/9.png)
现在我们文档中并没有图片,只有一个图片的`url`链接,咋整?不是问题,让我们看看`fabric.Image.fromURL`:
```js
fabric.Image.fromURL('my_image.png', function(oImg) {
canvas.add(oImg);
});
```
看起来是不是很直接?仅仅是通过`fabric.Image.fromURL`加载图片的`URL`,然后当图片加载完成时,给一个回调函数.这个回调函数接收已经创建完成的`farbic.Image`对象当做第一个参数.在这里,你可以直接将它添加到画布上,也可以对其做一些修改然后再添加到画布上.
```js
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);
});
```
### 路径`Paths`
我们已经了解了简单的形状和图片.那么更复杂和丰富的形状呢?
让我们看看这强大的一对---`Path和Groups`.
`Fabric`中的路径表示可以通过其他方法填充修改的轮廓形状.路径是由一系列从一点到另一点的指令组成.通过这些指令,例如`move`,`line`,`curve`,`arc`等等,路径可以绘制出令人难以想象的复杂形状.再加上`PathGroup`的帮助,还可以有更多的扩展.
`Fabric`中路径类似`svg`元素.他们使用相同的一组命令,可以从`<path>`元素创建它们,并将它们序列化.稍后我们会更深入地观察序列化和SVG解析,但是现在值得一提的是,您很可能很少手工创建`Path`实例.相反,您将使用`Fabric`的内置SVG解析器。但是为了理解什么是Path对象,让我们尝试手工创建一个简单的对象:
```js
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);
```
![](http://fabricjs.com/article_assets/10.png)
我们在实例化时,传入了一个路径参数的字符串.虽然看起来神兮兮的,但还是蛮好理解的.`M`是`move`命令,告诉那支无形的画布移动到`0,0`的位置.`L`是`Line`命令,让画笔画一条线到`200,100`,然后另一个`L`命令,是使画笔画到`170,200`,`z`命令是强制关闭当前的路径,完成图形的绘制.结果我们获得是一个三角形.
既然`fabric.Path`跟其他`fabric`对象看起很像,所以我们可以改变一些它的属性:
```js
...
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);
```
![](http://fabricjs.com/article_assets/11.png)
```js
...
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 }));
```
![](http://fabricjs.com/article_assets/12.png)
好了,现在我们加载`SVG`元素可以使用`fabric.loadSVGFromString`or`fabric.loadSVGFromURL`.
### 后话