企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
:-: **介绍一** [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`. ### 后话