今天我要向你们介绍Fabric.js—一个能够让你轻而易举操作canvas的神奇的库.Fabric不仅提供了一个虚拟canvas对象,还有svg渲染器,交互层,还有一整套十分有用的工具.这是一个完全开源的项目,MIT协议,多年以来依靠许多贡献者共同维护.
Fabric开始与2010,在经历过原生canvas繁琐的API操作之后.原作者就写了一个可交互的编辑器printio.ru—允许用户自定义外观.那时候只有flashapp需要这种交互.光阴似箭,经过一点点积累形成了现在的Fabric.
让我们进一步看一看!
为什么选择fabric现在的Canvas支持我们去创造一些充满创造力神奇的图形但是它提供的api实在是水平低到令人发指.如果我们只是想画一些简单的图形.但是却需要一系列操作,各种修改中心点,如果要画一个复杂图形—那操作就“更有意思了”.
Fabric的目标就是解决这些问题.
原生canvas方法只允许我们使用一些简单的图形操作,然后在画布上瞎子摸象.想画一个矩形使用fillRect(left,top,width,height).想画一条线用moveTo(left,top)和lineTo(x,y)组合.这感觉就像用画笔在画布上画画,随着画的越来越多,画布内容的可控性就越差.
为了避免这种低水平操作,Fabric在基础上提供了一个简单且强大的对象模型.更注重于画布的状态和渲染,现在,让我们开始学习使用“对象”吧.
让我们通过一个简单的例子画一个红色的矩形来看看两者有什么不同.这是原生
//获取画布的引用varcanvasEl=document.getElementById('c');//获取2dcontext对象用来操作(之前提到的bitmap)varctx=canvasEl.getContext('2d');//给当前上下文设置颜色ctx.fillStyle='red';//在100,100的位置创建一个20*20的矩形ctx.fillRect(100,100,20,20);现在,让我们看看fabricjs怎么实现同样的效果://包裹一下canvas(withid="c")varcanvas=newfabric.Canvas('c');//新建一个矩形对象varrect=newfabric.Rect({left:100,top:100,fill:'red',width:20,height:20});//将矩形添加到canvas里canvas.add(rect);目前为止,最不同的地方在尺寸的设置—两个例子很像.但是你应该也意识到两种操作思想的不同了吧.用原生方法,我们操作context上下文—代表着整个canvas.用Fabric,我们在具体对象上操作—实例化它们,修改他们的属性,然后给它们添加到canvase上.这些对象是fabricjs世界的第一公民.
但是画一个红色的矩形没啥难度.我们来整点有意思的!比如,稍微的旋转一下
我们试试旋转45度.首先,使用原生
varcanvasEl=document.getElementById('c');varctx=canvasEl.getContext('2d');ctx.fillStyle='red';ctx.translate(100,100);ctx.rotate(Math.PI/180*45);ctx.fillRect(-10,-10,20,20);接下来使用fabric:varcanvas=newfabric.Canvas('c');//createarectanglewithangle=45varrect=newfabric.Rect({left:100,top:100,fill:'red',width:20,height:20,angle:45});canvas.add(rect);
发生了什么
我们只需要修改对象的's“angle”为45.使用原生方法,事情变得越来越“有趣了”.我们不能操作对象.反而为了实现需求,我们给整个canvasbitmap旋转了(ctx.translate,ctx.rotate).然后在画矩形上去,别忘了原点定位为(-10,-10),这样才能看起来是在(100,100).
现在我确信你已经清楚了fabricjs存在的意义,还有他帮助我门减少了多少低级代码.
让我们再看另一个例子—追踪canvas状态.
假设在某一个点,我们想要移动刚才的矩形到canvas上的另一个点如果不能操作对象,我们会怎么做是不是只能再调一遍fillRect
不仅如此.在调用另一个fillRect时候,我们在画布上画了一个新的矩形,但是现在已经有一个了.记得我之前提到的擦出功能吗为了“移动”,我们必须先擦除之前的画布,然后在新的位置画新的矩形.
varcanvasEl=document.getElementById('c');...ctx.strokRect(100,100,20,20);...//清除整个画布ctx.clearRect(0,0,canvasEl.width,canvasEl.height);ctx.fillRect(20,50,20,20);用fabric怎么实现varcanvas=newfabric.Canvas('c');...canvas.add(rect);...rect.set({left:20,top:50});canvas.renderAll();注意最重要的区别.用过Fabricjs,我们不用再为了实现“移动”清除上一个画布.只需要操作对象,简单的修改它们的属性,然后re-rendercanvas获取“最新的画面”.
对象现在我们已经了解了怎么操作fabric.Rect构造函数生成实例.当然Fabric默认包含了许多基础形状—原,三角,椭圆,等等.这些都挂载在fabric“变量下”像fabric.Circle,fabric.Triangle,fabric.Ellipse,等等.
Fabric提供的7中基础图形:
fabric.Circle圆fabric.Ellipse椭圆fabric.Line线段fabric.Polygon多边形fabric.Polyline折线fabric.Rect矩形fabric.Triangle三角形想要画一个圆只需要创建圆对象,然后添加到canvas中.和其他基础图形一样:
varcircle=newfabric.Circle({radius:20,fill:'green',left:100,top:100});vartriangle=newfabric.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})从之前的位置“移动”走了.类似的方式,我们可以修改任何属性.但是有啥属性呢
是的,在fabric中创建翻转对象只需要给flip*属性设置为true.
你可通过getmethod读取所有属性,然后使用set方法.让我们试试修改红色矩形的属性:
varcanvas=newfabric.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”,将图像变成红色的了.下一行设置了“画笔宽度”和“画笔颜色”的值,给矩形一个5px宽的浅绿色边框.最后,我们修改“angle”和“flipY”属性.注意三行不同的设置语法,都支持.
这个例子展示了set方法的通用性.你以后会经常用到它,所以这个方法尽量的支持各种使用方法.
讲完了设置属性,那么如果获取呢只需要使用通用的get方法,当然还有各种特殊的get*,来获取一个属性.想要获取一个对象的“width”,可以用get('width')或者getWidth().想要获取“scaleX”属性—get('scaleX')或者getScaleX(),等等.对象“公共”属性都有getWidth或getScaleX这种方法(“stroke”,“strokeWidth”,“angle”,等.)
你可能注意到了,在之前的例子里写初始化配置生成的对象和使用set方法创建的没什么区别.这是因为他们就是完全一样.你可以在初始化的时候使用“配置”,或者在创建对象之后再使用set方法:
varrect=newfabric.Rect({width:10,height:20,fill:'#f55',opacity:0.7});//一样的varrect=newfabric.Rect();rect.set({width:10,height:20,fill:'#f55',opacity:0.7});默认配置到这里,你可能想问—不传“配置”创建对象发生了什么.还有那些属性吗
当然有.Fabricjs中的对象一直会有默认属性的.如果在创建的时候省略,就是使用默认值.试试看:
varrect=newfabric.Rect();//没有配置传入
rect.get('width');//0rect.get('height');//0
rect.get('left');//0rect.get('top');//0
rect.get('fill');//rgb(0,0,0)rect.get('stroke');//null
rect.get('opacity');//1这个矩形都使用了默认值.定位在0,0,黑色,完全不透明,没有边框没有尺寸(宽高都是0).因为都是0,所以我们看不见.只要给宽高附上正整数后,我们就能看在一个黑色矩形在画布左上角.
HierarchyandInheritanceFabric对象并不是独立存在的.他们都继承自一个源对象
大多数对象都继承自根对象fabric.Object.fabric.Object代表着一个二维,有着坐标和宽高,以及一系列其他图形特征.这些就是之前看到的对象属性—fill,stroke,angle,opacity,flip*,等.
使用继承,可以让我们在fabric.Object上定义方法,然后提供给所有子类.比如,你想给所有对象都加一个自定义getAngleInRadians方法,你可以直接在fabric.Object.prototype上定义
fabric.Object.prototype.getAngleInRadians=function(){returnthis.get('angle')/180*Math.PI;};varrect=newfabric.Rect({angle:45});rect.getAngleInRadians();//0.785...varcircle=newfabric.Circle({angle:30,radius:10});circle.getAngleInRadians();//0.523...circleinstanceoffabric.Circle;//truecircleinstanceoffabric.Object;//true正如你所见,这个方法立刻在所有实例上都生效了.
当创建子“类”的时候,子类上经常要定义一些属于自己的属性和方法.例如,fabric.Circle需要“radius”属性.fabric.Image—在下面会讲到—需要getElement/setElement方法用于访问/设置HTML元素.在高级项目中使用原型来获取自定义渲染和行为非常常见.
Canvas现在已经详细的说完了Fabricjs对象,让我门回过头来说一下canvas.
在所有的Fabricjs例子中,你看到的第一行是不是创建canvas对象?—newfabric.Canvas('...').fabric.Canvas包裹着
我们可以将对象add进去,通过引用关系,也可以删除他们:
varcanvas=newfabric.Canvas('c');varrect=newfabric.Rect();canvas.add(rect);//添加进去canvas.item(0);//获取刚才添加的fabric.Rectcanvas.getObjects();//获取画布中所有的对象canvas.remove(rect);//删除fabric.Rect所以fabric.Canvas的主要作用就是管理对象,它还可以写一些配置.想要给画布设置背景对所有内容进行裁剪设置不同的宽/高是否可交互包括但不限于这些属性可以传给fabric.Canvas,同对象一样,在任何时候都可以:
对象模型的存在允许以编程方式访问和操作画布上的对象.但是对于用户来说,需要用鼠标或者手指操作.当你通过newfabric.Canvas('...')初始化画布之后,可以选择拖动旋转缩放等,甚至是群选之后一起操作!
如果我们想让用在在画布上拖拽一些东西—比如一张图片—我们只需要创建画布,加一个图片进去.不需要任何额外的操作.
我们可以使用把布尔值传给Fabric's“selection”或者对象的穿一个布尔值给对象的“selectable”字段来控制是否可交互.
varcanvas=newfabric.Canvas('c');...canvas.selection=false;//关闭群选rect.set('selectable',false);//单个对象不可选如果不想要这个交互功能你可以使用fabric.StaticCanvas代替fabric.Canvas.别的都一样.
varstaticCanvas=newfabric.StaticCanvas('c');staticCanvas.add(newfabric.Rect({width:10,height:20,left:100,top:100,fill:'yellow',angle:30}));这样创建了一个“轻量”版本的canvas,没有任何事件处理逻辑.你还是可以操作全部的对象模型—添加对象,删除或修改他们,或者修改canvas配置—这些都还是一如既往能用.只是事件系统没了.
总之,如果你需要一个不需要交互的画布,就要择更轻量的StaticCanvas就够了.
图片说到图片…
在画布上玩矩形圆形没什么意思,我们来试试玩图片正如你认为的,Fabric也让这个变得简单.让我们实例化一个fabric.Image对象然后把它添加进canvas:
(html)
varcanvas=newfabric.Canvas('c');varimgElement=document.getElementById('my-image');varimgInstance=newfabric.Image(imgElement,{left:100,top:100,angle:30,opacity:0.85});canvas.add(imgInstance);注意,我们将一个图片元素纯给了fabric.Image构造函数.这样就创建了fabric.Image的实例.并且,我们立刻设置了图片的坐标、旋转、透明度.加入到画布中后,会看见一个图片在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){//scaleimagedown,andflipit,beforeaddingitontocanvasoImg.scale(0.5).set('flipX',true);canvas.add(oImg);});路径我们先了解了简单的图形,然后是图片.下面看看更复杂的图形和内容
首先看一对强力组合—路径和分组.
在Fabric中,路径代表着一个可以背修改,填充,描边的形状.路径是由一堆命令组成的,本质上是在模仿一支笔从一个点到另一个点.通过“move”,“line”,“curve”,或者“arc”命令,可以组成神奇的图案.借助路径的分组功能Paths(PathGroup's),让用户发挥想象的空间就更大了.
Fabric中的Paths与SVG
varcanvas=newfabric.Canvas('c');varpath=newfabric.Path('M00L200100L170200z');path.set({left:120,top:120});canvas.add(path);
我们实例化了一个fabric.Path对象,给它传了一串字符串路径指令.虽然看起来神秘,但是它其实很容易理解.“M”代表“move”命令,命令那只不可见的笔指在0,0的位置.“L”代表着“line”用笔画到200,100的位置.然后,另一个“L”画了一条170,200的线.最后,“z”命令画笔闭合这条线段,确定最终形状.这样我们就得到了一个三角形.
显而易见fabric.Path只是Fabric中的另一种对象,我们同样可以修改他的属性.但是我们可以改的更多:
...varpath=newfabric.Path('M00L300100L200300z');...path.set({fill:'red',stroke:'green',opacity:0.5});canvas.add(path);出于好奇,让我们试一试稍微复杂一点的图形.你会发现我之前说的对,没有办法手写路径.
...varpath=newfabric.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”还是代表着“move”,所以画笔从“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文档,Fabric的path代表着SVG
就像使用路径一样,您可能不会直接使用它们.但是一旦你需要,你应该知道它的原理.
后记我们只是介绍了Fabric一些基础的东西.你现在可以轻松的在canvas上操作简单的复杂的图形,图片了。—位置,尺寸,旋转,颜色,边框,透明度.
该系列的下一章,我们会讲组;动画;文班;SVG解析,渲染,序列化;事件;图片滤镜等等.
与此同时,去看看示例或者基础数据或者别的地方,或者直接看文档,wiki,和源代码.