今天,我想向您介绍Fabric
.js——一个强大的javascript库,它能够让使用HTML5 canvas变得轻而易举。Fabric
为canvas提供了一个跟踪的对象模型,以及一个SVG解析器、交互层和一整套其他不可缺少的工具。
这是一个完全开源的项目,由麻省理工学院授权,多年来做出了许多贡献。
web Canvas提供一些非常好的图像API,但是这些API实在是过于原生.如果我们只是在画布画了图像之后就无法重新修改了,就需要全部重画,或者在原本的画布添加新的图形,尤其是一旦需要任何形式的互动,在任何一点上改变画面,或者画出更复杂的形状——位置变换就会发生预期之外的变化。
原生的canvas方法只允许我们使用一些简单的图形命令,笨笨地修饰整个画布.比如想要画出一个正方形,使用fillRect(left, top, width, height)
.即可.想要画出一条线?使用2个组合命令moveTo(left, top)
和 lineTo(x, y)
.就像我们使用画笔画画一样,不断地在一个画布上加颜色和图片,一旦画上了想要修改就会很头疼
Fabric提供了简单但是非常实用的对象模型,以此代替一些简单的原生canvas指令.它更关心canvas的状态和渲染.我们下面就来看下Fabric是如何让canvas和对象一起工作的吧.
让我们来看下下面2种方式的不同,当我们需要在canvas上画一个红色的正方形,下面是我们调用原生的API的代码
// 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的版本
// 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);
现在看起来,似乎代码量并没有区别.无论如何,你可以明显地发现2个版本的画出红色方块的区别
下面的例子我们就加多一点"特技"看看:
旋转45deg,使用原生API
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中,我们只需要在对象中添加一对数值angle和对应的值就可以设定方块旋转的角度.而在原生API中,我们的操作变得有点复杂,由于我们不是对方块这个对象直接进行旋转的,而是旋转整个画布,所以就会导致生成的图形位置不符合预期的,所以在最后绘图的时候,我们需要将画布稍微移动下,图形需要偏移(-10,-10)
才能正确地绘画到(100,100)
除此之外,我们并不能直接使用角度的计量,而是需要多进行一步转换,原生API旋转角度是弧度制的.
现在你应该了解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的时候,我们不再需要清除画布来"更新"画布.我们仍然只需要在方块对应的对象进行修改,然后调用renderAll
更新画布,就能非常简单地完成移动的操作
我们已经了解了fabric.Rect
的构造函数,当然出了矩形之外,fabric也封装了许多的图形方法:圆、三角形、椭圆等等.他们全部都暴露在fabric的作用域中,你可以直接这样调用它:fabric.Circle
、fabric.Triangle
7个基本的图形API
想要画一个圆?只需要创建一个圆的对象,并把它添加到canva,即可.其他图形也一样
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);
创建一个图形对象——矩形,圆,或者其他东西.
创建完成之后,当我们需要修改这个图形在画布的位置或大小等变化,比如我们需要给三角形添加一个移动的动画或者是修改三角形的颜色等.
fabric
的原理就是canvas渲染与我们设置的图形信息相关,所以我们只需要修改图形的信息即可.
我们就需要使用上一个移动红色方块案例所演示的set
方法,相似地,我们可以通过set
图形信息来改变图形在canvas
的样子
我们能修改的信息:
位置:{left:0,top:0}
宽高度:{width:0,height:0}
填充的颜色和边框的样式{fill,color},{ strokeWidth: 5, stroke: 'rgba(100,200,200,0.5)'}
缩放和旋转{angle:15,scaleX:0,scaleY:0}
拉伸{skewY:0,skewX:0}
翻转{flipX:true,flipY:false}
这说明set是一种通用方法。你可能会经常用到它,所以在设计的时候会尽可能地设计成方便使用.比如set
返回的对象是this
,所以你可以连续调用这个方法就像这样rect.set('angle', 15).set('flipY', true);
有设置的方法,就会有获取的方法.为了方便获取图形对象的信息,有通用的get方法,也有一些特定的get*方法。
要读取对象的“width”值,可以使用get(“width”)
或getWidth()
获取一个“scaleX”值——get('scaleX')
或getScaleX()
,依此类推。
如果我们声明一个图形对象的实例时没有设置属性时,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
上自定义所有类型图形对象公用的方法.比如如果你想要自定一个获取旋转角度(angle属性)
对应的弧度值的方法.你可以将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
如您所见,方法立即在所有实例上可用。
我们已经介绍对象足够详细了,现在我们回到canvas画布上.
你可以清楚地看到例子上创建canvas
对象的方法new fabric.Canvas('...')
canvas
对象提供一个封装canvas(element)
并且返回管理所有fabric对象
的特殊canvas
它接受canvas
的id
,并返回fabric.Canvas
的实例。
使用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.canva
设置的时候,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
亮点特性之一
在内部,我们可以通过代码主动修改图形对象的各个属性,而在外部,我们通过点击等操作也可以修改图形对象
只要你初始化canvas
是通过声明new fabric.Canvas('...')
,你就可以看到你能够点击、选取、缩放、旋转每一个图形,甚至你可以选取多个进行同时操作
当然,你可以单独对图形对象取消这些功能
var canvas = new fabric.Canvas('c');
...
canvas.selection = false; // disable group selection
rect.set('selectable', false); // make object unselectable
当然,你也可以完全不使用交互.你在添加图形对象的时候使用的是fabric.Canvas
中的静态画布 fabric.StaticCanvas
.
var staticCanvas = new fabric.StaticCanvas('c');
staticCanvas.add(
new fabric.Rect({
width: 10, height: 20,
left: 100, top: 100,
fill: 'yellow',
angle: 30
}));
这创建了一个“轻量级”的canvas版本,没有任何事件处理逻辑。但是这样只是减少了事件处理,你仍然能够进行图形对象的修改.
直接看例子
<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
添加一个image(element)
到canvas
画布上,这个图片数据来源于html
文档.此外,我们还可以对image设置位置left/top
宽高度width/height
,旋转角度angle
透明度opacity
等
fabric.Image.fromURL
我们不仅能够使用html
文档中的图片,还能通过URL
获取图片信息,让我们看看如何使用fabric.Image.fromURL
fabric.Image.fromURL('my_image.png', function(oImg) {
canvas.add(oImg);
});
看起来非常简单,就如同名字一样,Image.fromURL
来自URL
连接的图片,第二个参数是一个回调函数,因为URL需要一定的下载事件,所以需要等待图片下载完成才能够成功地将图片添加到CANVAS
画布上
回调函数的参数就是图片对象,你可以设置图片的参数从而改变图片在canvas
的渲染样式
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);
});
我们了解了图形、图片,如果我们想要更复杂的图形,路径就是我们需要使用到的工具了.
路径就是图片的外轮廓,它可以被填充、边框和其他方式修改.
路径有一系列模拟画笔连接线的命令组成,在move
, line
, curve
, 和 arc
,路径在路径组的帮助下,可以设计很复杂的图形
Fabric中的PATH与SVG 元素非常相似。它们使用相同的命令集,可以从 元素创建它们,并将它们序列化。稍后我们将更深入地研究序列化和SVG解析但是现在值得一提的是,您可能很少手工创建Path实例。相反,您将使用Fabric的内置SVG解析器。但是为了理解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);
我们实例化fabric.Path
对象,传入一些字符串参数。虽然看起来很复杂,但是一但明白了规则理解起来就非常简单了。
“M”表示“move”命令,在这个例子中意味着从0,0
开始。
“L”代表“直线”,让钢笔画一条直线到200,100
。
然后,另一个“L”创建一条到177,200
的线
“z”指示绘图笔关闭当前路径并最终确定形状。
明白其中的规则之后,就可以非常快速明白这个字符串的意思。
在使用path路径画出的图形,我们一样可以像修改其他图形对象一样,使用 set
设置图形对象的属性
...
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
和L
代表这MOVE
和LINE
,这里的C
表示贝赛尔曲线,它的作用就是画出曲线。它使笔绘制贝塞尔曲线从当前点到“36.67,0”。它在行首使用“29.5,3.22”作为控制点,在行尾使用“24.31,8.41”作为控制点。接下来是其他十几条立方体bezier命令,它们最终创建了一个漂亮的箭头形状
当然,你不会这么笨地一点点画这些路径组,你可以加载svg文件来代替这种笨方法。loadSVGFromString
或者svgfromurl
方法来加载SVG文件,让Fabric的SVG解析器来遍历所有SVG元素并创建相应的Path对象。
说到SVG文档,而Fabric的路径通常表示SVG < Path >元素,SVG文档中经常出现的路径集合被表示为组(fabric.Group
实例)。可以想象,Group
只是一组路径和其他对象。由于fabric.Group
继承自fabric.object
,它可以像其他对象一样添加到画布中,并以相同的方式进行操作。
或许你开发会比较少使用它,但是你也会偶然发现SVG文档,你只需要知道它是什么,有什么用的就行了。
目前,我们已经学习了fabric
的一些基础知识,你现在可以很容易地创建任何简单的形状,复杂的形状,图像;将它们添加到画布中,并以任何您想要的方式进行修改——位置、尺寸、角度、颜色、笔触、不透明度——您可以对其进行命名。
在本系列的下一部分中,我们将了解如何使用组;动画;文本;SVG解析、渲染、序列化;事件;图像过滤器;和更多。