Fabric.js 是一个简化HTML5 Canvas开发的Javascript库,Fabric.js提供了HTML5 Canvas本身缺失的对象模型、交互层、SVG解析器以及其他一整套工具。它是一个完全开源的项目,在MIT下获得授权,多年来一直在维护,近期要发布4.0版本,支持自定义controls。
在Canvas画布上创建、填充图形(包括图片、文字、规则图形和复杂路径组成图形)。
给图形填充渐变颜色。
组合图形(包括组合图形、图形文字、图片等)。
设置图形动画及用户交互,生成的对象自带拖拉拽功能。
JSON, SVG数据的序列化和反序列化。
HTML5 Canvas提供了完整的画布,可以轻松的在画布上绘制简单的图形、制作简单的动画,但是HTML5 Canvas提供的API过于低级,且操作基于上下文,因此在绘制复杂图形或者需要实现用户交互时,就显得不是那么方便了。Fabric.js在HTML5 Canvas原生API之上,提供完整的对象模型,由Fabric.js来管理HTML5 Canvas画布的状态和渲染,用户基于具体的对象,来编写代码,完成所需功能的开发(类似于面向过程和面向对象)。“Talk is cheap. Show me the code”,下面通过代码来看看用HTML5 Canvas原生方法和用Fabric.js分别实现在画布上画一个矩形这一功能。
用HTML5 Canvas的原生方法实现
// 获取canvas元素
var canvasEl = document.getElementById('c');
// 获取上下文
var ctx = canvasEl.getContext('2d');
// 设置填充颜色
ctx.fillStyle = 'red';
// 在100,100点创建20x20的矩形
ctx.fillRect(100, 100, 20, 20);
在线演示地址:https://codepen.io/liblack/pen/LYpaMwa
用Fabric.js实现相同的功能:
// 创建原生canvas元素的包装类(‘c’canvas元素的id)
var canvas = new fabric.Canvas('c');
// 创建一个矩形对象
var rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'red',
width: 20,
height: 20
});
// 添加矩形到画布上
canvas.add(rect);
在线演示地址:https://codepen.io/liblack/pen/PoPLVwZ
由上面例子,可以看到使用HTML5 Canvas原生方法是对context(代表整个画布位图的对象)进行操作,而使用Fabric.js,我们是对对象操作,只需要去实例化对象,设置对象的属性,然后将它们添加到canvas中。到这里,可能还感受不到Fabric对比原生Canvas方法的优势,下面可以完成这样的功能,即在刚才绘制的矩形的基础上,将矩形位置移动到(10,10)点上。
这样的功能,在Canvas原生方法上实现,需要先把原来的矩形从画布上清除,然后在(10,10)位置重新绘制一个矩形,以此来实现矩形移动这样的功能。
// 擦除之前的矩形(这里是擦除了整个canvas区域)
ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
// 重新绘制矩形
ctx.fillRect(20, 50, 20, 20);
听起来是不是很蹩脚?是的,使用Canvas原生方法时,就像是个带橡皮檫的笔刷,在画布上绘制图形时,是笔刷在画布上移动,绘制出的图形就是笔刷移动的整个痕迹,而想把绘制好的图形移动到其他地方,只好用橡皮檫擦掉,然后在新位置重新绘制。
相应的,使用Fabric.js实现该功能时,只需要改变矩形对象的属性,然后重新刷新(渲染)画布即可。
rect.set({ left: 20, top: 50 });
canvas.renderAll();
fabric.Canvas作为元素的包装器,创建fabric.Canvas对象如下:
var canvas = new fabric.Canvas('...')
它接收一个元素的id,并返回一个fabric.Canvas 的实例。fabric.Canvas对象,并不是DOM里的元素,如果需要直接控制DOM,或者对应的context,需要通过相应API去获取。
var canvasElement = document.getElementById(canvasEle);
var ctx = canvasElement.getContext("2d");
fabric.Canvas对象负责管理画布上绘制的所有对象,可以将对象添加到fabric.Canvas对象,也可以从fabric.Canvas获取或删除对象。
var canvas = new fabric.Canvas('c');
var rect = new fabric.Rect();
canvas.add(rect); // 添加对象
canvas.item(0); ///获取之前添加的fabric.Rect(第一个对象)
canvas.getObjects(); ///获取画布上的所有对象(rect将是第一个也是唯一一个)
canvas.remove(rect); ///删除之前添加的对象删除
fabric.Canvas对象可以对画布进行配置。比如需要为整个画布设置背景颜色或图像?需要将所有内容剪裁到特定区域?设置不同的宽度/高度?指定画布是否是交互式的?所有这些选项(及其他)都可以在fabric.Canvas对象上进行设置,可以在创建时或之后进行设置。
var canvas = new fabric.Canvas('c', {
backgroundColor: 'rgb(100,100,100,200)',
selectionColor:'blue',
selectLineWidth: 2
// ...
});
// 或
var canvas = new fabric.Canvas('c');
canvas.setBackgroundImage('http://...');
canvas.onFpsUpdate = function(){ /* ...... */ };
// ...
注意这种创建对象的形式,Fabric.js里基本上都是类似的,类名表示要创建的对象类型,第一个参数是必要的数据,第二个参数是各种选项。
所有对canvas的修改,包括在其中添加删除对象,以及对象参数的修改,都需要调用渲染方法才能显示出来:
canvas.renderAll();
Objects基本图形
Fabric.js提供了7种基本图形,下面是图形对应的类,
fabric.Circle
fabric.Ellipse
fabric.Line
fabric.Polygon
fabric.Polyline
fabric.Rect
fabric.Triangle
所有基本形状,都可以通过类实例的属性和方法来控制它们的位置、颜色、大小等样式。所有类都继承自fabric.Object类,有一些公共的属性和方法。
下面是画线的例子(给出两个顶点坐标):
var line = new fabric.Line([x1, y1, x2, y2], {
strokeWidth: 2, //线宽
stroke: rgba(255,0,0,0.8), //线的颜色
selectable: false
});
canvas.add(line);
画圆的例子(顶点和半径是在选项里的),这里left和top其实就是(x,y),应该是借用了css里的命名。
var circle = new fabric.Circle({
radius: 2,
left: left,
top: top,
originX: 'center',
originY: 'center',
fill: rgba(0,200,0,0.8),
strokeWidth: 1,
stroke: rgba(255,0,0,0.8),
selectable: false
});
canvas.add(circle);
从这里可以看出,和创建canvas类似,第一个参数是这个类专用的(比如画直线的时候传的起止点坐标),第二个参数是通用选项,如果没有专用参数,那么第一个参数就直接是通用选项。所有创建完的形状,只有通过canvas.add方法加入,才能显示出来。
left和top是每种Object都有的属性,至于它到底指图形中哪一个点的坐标,由originX和originY这组参数决定,它们相当于文本编辑软件里的对齐方式,originX有三种可选值:left,center, right;originY也有三种可选值:top, center, bottom。
它们的示意图如下:
http://fabricjs.com/test/misc/origin.html
如果希望对象的默认原点在中心,可以这样设置:
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center'
width和height也是可以直接存取的属性,顾名思义,表示长和宽(所有形状都是有外接矩形的,所以可以用长和宽来控制大小)。除了上面那几个可以直接存取的属性,大部分属性需要使用get/set方法读写,比如:
line.left = pointer.x;
line.top = pointer.y;
line.set('stroke', startColor);
line.set('height', 20);
Image跟其他形状类似,都是fabric.Object的子类,最大的区别在于,图像文件的加载是异步的,所以对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.js中的Path代表了一个形状的轮廓,它可以被填充、描边和修改。Path由一系列的命令组成,本质上是模仿一支笔从一个点到另一个点。在“move”, “line”, “curve”, or “arc”等命令的帮助下,Path可以形成非常复杂的形状。而在Paths(PathGroup’s)组的帮助下,Path的能实现更多的复杂图形。
Fabric.js中的Path与SVG的
手工创建一个简单的Path对象。
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);
实践中,更多是直接使用fabric.loadSVGFromString 或 fabric.loadSVGFromURL 方法来加载整个 SVG 文件,然后让 Fabric 的 SVG 解析器来完成所有 SVG 元素的解析工作,并创建相应的 Path 对象。
事件驱动的架构是一个框架灵活性和可扩展性的基础。Fabric.js提供了一个广泛的事件系统,覆盖了低级的 “鼠标”事件到高级的对象事件。
这些事件使我们能够监控到到画布上发生的各种动作的不同时刻。比如想知道鼠标被按下的时间,只需监听”mouse:down”事件就可以了。想知道对象何时被添加到画布上,只需要监听”object:added “事件就在那里。
事件API非常简单,类似于jQuery、Underscore.js或其他流行的JS库。有on方法来初始化事件监听器,有off方法来移除事件监听器。
下面一个实际的例子。
var canvas = new fabric.Canvas('...');
canvas.on('mouse:down', function(options) {
console.log(options.e.clientX, options.e.clientY);
});
上面例子演示了在canvas上添加”mouse:down”事件监听器,并给它设置事件处理程序,它将记录事件发生的坐标。事件处理程序会接收一个选项对象,它有两个属性:e—原始事件,和target—画布上的点击对象(如果有的话)。该事件在任何时候都是存在的,但目标只有在实际点击了画布上的某个对象后才会存在。target也只有在有意义的情况下才会传递给事件的处理程序,例如,对于 “mouse:down”事件,但对于 “after:render”事件(表示整个画布被重新绘制),target不会传递给事件处理程序。
canvas.on('mouse:down', function(options) {
if (options.target) {
console.log('一个对象被点击了!', options.target.type);
}
});
如果你点击了一个对象,上面的例子会输出 “一个对象被点击了!”。还会显示被点击的对象类型。
那么,在Fabric.js中还有鼠标级的事件:“mouse:down”, “mouse:move”, 和”mouse:up”。通用的事件: “after:render”。选择相关的事件: “before:selection:cleared”,”selection:create”,”selection:cleared”。对象相关的事件: “object:moded”、”object:selected”、”object:moving”、”object:scaling”、”object:rotating”、”object:additional “和 “object:detter”。
注意,像”object:moving”(或”object:scaling”)这样的事件在每次对象移动(或缩放)时,即使是一个像素点的移动,也会连续地被触发。另一方面,像 “object:modified” 或 “selection:create”这样的事件只在操作(对象修改或选区创建)结束时才会被触发。如果将事件直接附加到画布上的(canvas.on(‘mouse:down’, …)),这意味着事件被覆盖到了canvas实例上。如果一个页面上有多个画布,可以给每个画布附加不同的事件监听器。它们都是独立互不影响的。
为了方便,Fabric.js将事件系统做得更进一步,允许直接将监听器附加到具体对象上。如下例子。
var rect = new fabric.Rect({ width: 100, height: 50, fill: 'green' });
rect.on('elected', function() {
console.log('选择了一个矩形');
});
var circle = new fabric.Circle({ radius: 75, fill: 'blue' });
circle.on('selected', function() {
console.log('选择了一个圆圈');
});
上述例子直接给矩形和圆的实例附加事件监听器,使用的是 “selected “事件,而不是 “object:selected”。同样的,可以在对象上使用 “modified”事件(当附加到canvas上时使用 “object:modified”)、”rotating”事件(当附加到canvas时使用 “object:rotating”)等等。
fabric.Group是Fabric.js提供的强大的功能之一。使用Groups可以将任何Fabric对象组合成一个单一实体,这样就能够将这些对象作为一个单一的单元来处理。用鼠标将画布上的任意数量的Fabric对象进行组合,一旦组合后,这些对象都可以一起移动甚至修改。它们组成了一个组。我们可以对该组进行缩放、旋转,甚至改变其呈现属性—颜色、透明度、边框等。
下面创建一个由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(group);
组成组以后,依旧可以对每个对象操作,改变对象的属性和状态。
group.item(0).set('fill', 'red');
group.item(1).set({
text: 'trololo',
fill: 'white'
});
为构建某种有状态的应用程序,允许用户将canvas内容的结果保存在服务器上,或者将内容流媒体化到不同的客户端。Fabric.js提供了canvas序列化/解序列化支持。
Fabric中序列化的主要方法是 fabric.Canvas#toObject()和 fabric.Canvas#toJSON()方法。让我们来看一个简单的例子,首先对一个空画布进行序列化。
var canvas = new fabric.Canvas('c');
JSON.stringify(canvas); // '{"objects":[], "background": "rgba(0,0,0,0,0)"}'
使用的是ES5 JSON.stringify()方法,如果传入的对象存在toJSON方法,那么这个方法就会隐含地调用toJSON方法。由于Fabric中的canvas实例有toJSON方法,所以调用JSON.stringify(canvas)方法和调用JSON.stringify(canvas.toJSON())一样。
注意,返回的字符串表示空的canvas。它是JSON格式的,本质上由 “objects”和”background”属性组成。”objects”目前是空的,因为canvas上没有任何东西,而 background 有一个默认的透明值(“rgba(0,0,0,0,0)”)。
当在canvas上添加了具体对象后:
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)"}'
Fabric.js支持将canvas画布序列化为SVG格式的文本。
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>'
与序列化类似,反序列化是从字符串中加载canvas,与序列化时相对应的,也有两种方法:从JSON文本反序列和从SVG文本反序列化。当使用JSON表示时,有 fabric.Canvas#loadFromJSON和 fabric.Canvas#loadFromDatalessJSON方法。当使用SVG时,
有 fabric.loadSVGFromURL和 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)"}');
下移:canvas.sendBackwards(obj)
上移:canvas.bringForward(obj)
置顶:canvas.bringToFront(obj)
置底:canvas.sendToBack(obj)
fabric.js官网:http://fabricjs.com/
fabric.js源码:https://github.com/fabricjs/fabric.js
fabric.js应用案例:https://printio.ru/tees/new_v2
HTML5 Canvas资料:https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial