├── README.md ├── part-1.md ├── part-2.md ├── part-3.md ├── part-4.md ├── part-5.md ├── part-6.md ├── part-7.md └── part-8.md /README.md: -------------------------------------------------------------------------------- 1 | # Fabric.js中文教程 2 | 3 | [Fabric官网](http://fabricjs.com/articles/) 4 | 5 | - [第一部分](./part-1.md) 6 | - 为什么要做fabric 7 | - Fabric中的对象 8 | - Canvas 9 | - 图像 10 | - 路径(Paths) 11 | - [第二部分](./part-2.md) 12 | - 动画(animate) 13 | - 图像滤镜(filters) 14 | - 颜色(Colors) 15 | - 渐变(Gradients) 16 | - 文本(Text) 17 | - 字体(fontFamily) 18 | - 事件(Events) 19 | - [第三部分](./part-3.md) 20 | - 组合(Group) 21 | - 序列化 22 | - 反序列化,SVG解析器 23 | - 子类 24 | - [第四部分](./part-4.md) 25 | - 自由绘画 26 | - 自定义 27 | - Node.js上的Fabric 28 | - [第五部分](./part-5.md) 29 | - 缩放和平移(Zoom and panning) 30 | - [第六部分](./part-6.md) 31 | - 变形(transform) 32 | - [第七部分](./part-7.md) 33 | - 子类化Textbox类以生成位图文本 34 | - [第八部分](./part-8.md) 35 | - clipPaths剪切基础 36 | - clipPaths嵌套和更复杂的场景 37 | - canvas上使用clipPaths -------------------------------------------------------------------------------- /part-1.md: -------------------------------------------------------------------------------- 1 | # Fabric.js介绍 第一部分 2 | 3 | ![ ](https://github.com/kangax/fabric.js/raw/master/lib/screenshot.png) 4 | 5 | 今天我想向您介绍[Fabric](http://fabricjs.com/),一个使得使用HTML5 canvas很容易的功能强大的Javascript库。Fabric为canvas提供了缺失的对象模型、SVG解析器、交互层以及一整套必不可少的工具。它是一个完全开放源码的项目,使用MIT许可协议,多年来做出了许多贡献。 6 | 7 | Fabric开发始于2010年,在发现使用原生canvas的API非常痛苦之后。之前的作者为[printio.ru](http://printio.ru/)创建一个互动设计编辑器:他的创业公司允许用户设计自己的服装。在那个时代,那种交互性只存在于Flash应用中。即使是现在,市面上也很少有工具像"Fabric"一样使这种交互性成为可能。 8 | 9 | 让我们看得更仔细些吧! 10 | 11 | ## 为什么选择fabric 12 | 13 | [Canvas](http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html)可以让我们在网络上创造出绝对惊人的图形。但它提供的API是令人失望的。如果我们只想在画布上画几条基本的形状,不会觉得有什么繁琐。但是一旦需要任何形式的互动,任何时候改变图片或绘制更复杂的形状,代码复杂度会急剧增加。 14 | 15 | Fabric旨在解决这个问题。 16 | 17 | 原生canvas方法只允许我们触发简单的图形命令,盲目的修改canvas的位图,想画一个矩形?使用```fillRect(left, top, width, height).```,想画一条线?使用```moveTo(left, top)``` 和 ```lineTo(x, y)```的组合命令,就好像我们**用刷子画画**,上层涂上越来越多的颜料,几乎没有控制。 18 | 19 | Fabric不是在这么低的层次上运行,而是在原生方法之上提供简单而强大的对象模型。它负责画布状态和渲染,并让我们直接使用绘制后的“对象”。 20 | 21 | 让我们来看一个简单的例子来说明这个差异。假设我们想在画布上画一个红色的矩形。以下是我们如何使用原生的`````` API。 22 | 23 | ```js 24 | // 有一个id是c的canvas元素 25 | var canvasEl = document.getElementById('c'); 26 | 27 | // 获取2d位图模型 28 | var ctx = canvasEl.getContext('2d'); 29 | 30 | // 设置填充颜色 31 | ctx.fillStyle = 'red'; 32 | 33 | // 创建一个坐标100,190,尺寸是20,20的矩形 34 | ctx.fillRect(100, 100, 20, 20); 35 | ``` 36 | 37 | 现在使用Fabric做同样的事情: 38 | 39 | ```js 40 | // 用原生canvas元素创建一个fabric实例 41 | var canvas = new fabric.Canvas('c'); 42 | 43 | // 创建一个矩形对象 44 | var rect = new fabric.Rect({ 45 | left: 100, 46 | top: 100, 47 | fill: 'red', 48 | width: 20, 49 | height: 20 50 | }); 51 | 52 | // 将矩形添加到canvas画布上 53 | canvas.add(rect); 54 | ``` 55 | 56 | ![ ](http://fabricjs.com/article_assets/1.png) 57 | 58 | 在这种情况下,这两个例子非常相似,大小几乎没有差别。但是,您可以看到使用canvas的方法有多么不同。使用原生方法,我们在**上下文**中操作(表示整个画布位图的对象),在Fabric中,我们操作对象,实例化它们,更改其属性,并将其添加到画布。你可以看到这些对象是Fabric中的**第一等公民**。 59 | 60 | 但渲染纯正的红色矩形就如此无聊。我们至少可以做一些有趣的事情!也许,稍稍旋转? 61 | 62 | 旋转45度,首先使用原生的canvas方法: 63 | 64 | ```js 65 | var canvasEl = document.getElementById('c'); 66 | var ctx = canvasEl.getContext('2d'); 67 | ctx.fillStyle = 'red'; 68 | 69 | ctx.translate(100, 100); 70 | ctx.rotate(Math.PI / 180 * 45); 71 | ctx.fillRect(-10, -10, 20, 20); 72 | ``` 73 | 74 | 使用Fabric: 75 | 76 | ```js 77 | var canvas = new fabric.Canvas('c'); 78 | 79 | // 创建一个45度的矩形 80 | var rect = new fabric.Rect({ 81 | left: 100, 82 | top: 100, 83 | fill: 'red', 84 | width: 20, 85 | height: 20, 86 | angle: 45 87 | }); 88 | 89 | canvas.add(rect); 90 | ``` 91 | 92 | ![ ](http://fabricjs.com/article_assets/2.png) 93 | 94 | 这里发生了什么? 95 | 96 | 我们在Fabric中所做的一切都是将对象的“角度”值更改为```45```。然而使用原生的方法,事情变得更加有趣,记住我们无法对对象进行操作,相反,我们调整整个画布位图(```ctx.translate```,```ctx.rotate```)的位置和角度,以适应我们的需要。然后,我们再次绘制矩形,但记住要正确地偏移位图(-10,-10),所以它仍然呈现在100,100点。作为练习,我们不得不在旋转画布位图时将度数转换为弧度。 97 | 98 | 我相信你刚刚开始明白为什么面料存在,以及它解决了多少低级写法。 99 | 100 | 如果在某些时候,我们想将现在熟悉的红色矩形移动到画布上稍微不同的位置怎么办?我们如何在无法操作对象的情况下执行此操作?我们会在canvas位图上调用另一个```fillRect```吗? 101 | 102 | 不完全的。调用另一个```fillRect```命令实际上在画布上绘制的东西所有之上绘制矩形。还记得我前边说的用刷子画画吗?为了“移动”它,我们需要先**擦除以前绘制的内容**,然后在新的位置绘制矩形。 103 | 104 | ```js 105 | var canvasEl = document.getElementById('c'); 106 | 107 | ... 108 | ctx.strokRect(100, 100, 20, 20); 109 | ... 110 | 111 | // 擦除整个画布 112 | ctx.clearRect(0, 0, canvasEl.width, canvasEl.height); 113 | ctx.fillRect(20, 50, 20, 20); 114 | ``` 115 | 116 | 我们如何用Fabric完成这个? 117 | 118 | ```js 119 | var canvas = new fabric.Canvas('c'); 120 | ... 121 | canvas.add(rect); 122 | ... 123 | 124 | rect.set({ left: 20, top: 50 }); 125 | canvas.renderAll(); 126 | ``` 127 | 128 | ![ ](http://fabricjs.com/article_assets/3.png) 129 | 130 | 注意一个非常重要的区别。使用Fabric,在尝试“修改”任何内容之前,我们不再需要擦除内容。我们仍然使用对象,只需更改其属性,然后重新绘制画布即可获得“最新图片”。 131 | 132 | ## Fabric中的对象 133 | 134 | 我们已经看到如何通过实例化```fabric.Rect```构造函数来处理矩形。当然,Fabric也涵盖了所有其他的基本形状:圆,三角形,椭圆等。所有的这些在就是Fabric“命名空间”下的:```fabric.Circle```,```fabric.Triangle```,```fabric.Ellipse```等。 135 | 136 | Fabric提供了7种基础形状: 137 | 138 | - [fabric.Circle](http://fabricjs.com/docs/fabric.Circle.html) 139 | - [fabric.Ellipse](http://fabricjs.com/docs/fabric.Ellipse.html) 140 | - [fabric.Line](http://fabricjs.com/docs/fabric.Line.html) 141 | - [fabric.Polygon](http://fabricjs.com/docs/fabric.Polygon.html) 142 | - [fabric.Polyline](http://fabricjs.com/docs/fabric.Polyline.html) 143 | - [fabric.Rect](http://fabricjs.com/docs/fabric.Rect.html) 144 | - [fabric.Triangle](http://fabricjs.com/docs/fabric.Triangle.html) 145 | 146 | 想画一个圆?只需创建一个圆形对象,并将其添加到画布。与任何其他基本形状相同: 147 | 148 | ```js 149 | var circle = new fabric.Circle({ 150 | radius: 20, fill: 'green', left: 100, top: 100 151 | }); 152 | var triangle = new fabric.Triangle({ 153 | width: 20, height: 30, fill: 'blue', left: 50, top: 50 154 | }); 155 | 156 | canvas.add(circle, triangle); 157 | ``` 158 | 159 | ![ ](http://fabricjs.com/article_assets/4.png) 160 | 161 | 这是一个绿色的圆形在100,100的位置,蓝色的三角形在50,50的位置。 162 | 163 | ### 操作对象 164 | 165 | 创建图形对象矩形,圆形或其他东西,当然只是开始。在某些时候,我们可能想修改这些对象。或许某些行为需要触发状态的变化,或进行某种动画。或者我们可能希望在某些鼠标交互中更改对象属性(颜色,不透明度,大小,位置)。 166 | 167 | Fabric为我们处理画布渲染和状态管理。我们只需要自己修改对象。 168 | 169 | 以前的例子演示了```set```方法,以及如何从对象前一个位置调用```set({left:20,top:50})```来移动对象。以类似的方式,我们可以改变对象的任何其他属性。但这些属性是什么? 170 | 171 | 那么,正如你所期望的那样,可以改变定位:**top**,**left**。尺寸:**width**,**height**。渲染: **fill**, **opacity**, **stroke**, **strokeWidth**。缩放和旋转:**scaleX**, **scaleY**, **angle**;甚至可以翻转:**flipX**,**flipY**。歪斜:**skewX**,**skewY**。 172 | 173 | 是的,在Fabric中翻转对象非常简单,将```flip[X|Y]```设置为```true```即可。 174 | 175 | 您可以通过```get```方法读取任何这些属性,并通过```set```进行设置。我们尝试改变一些红色矩形的属性: 176 | 177 | ```js 178 | var canvas = new fabric.Canvas('c'); 179 | ... 180 | canvas.add(rect); 181 | 182 | rect.set('fill', 'red'); 183 | rect.set({ strokeWidth: 5, stroke: 'rgba(100,200,200,0.5)' }); 184 | rect.set('angle', 15).set('flipY', true); 185 | ``` 186 | 187 | ![ ](http://fabricjs.com/article_assets/5.png) 188 | 189 | 首先,我们将“fill”值设置为“red”,使对象成为红色。下一个语句设置“strokeWidth”和“stroke”值,给出矩形为淡绿色的5px笔画。最后,我们正在改变“angle”和“flipY”的属性。注意每个3个语句中的每个语句使用的语法略有不同。 190 | 191 | 这表明```set```是一种通用的方法。你可能经常使用它,所以它被设计得尽可能的方便。 192 | 193 | 说了如何```set```的方法,那么如何获取呢?这就有了与之对应的```get```方法,h还有一些具体的```get*```,要读取对象的“width”值,可以使用```get('width')```或```getWidth()```。获取“scaleX”值使用```get('scaleX')```或```getScaleX()```,等等。对于每个“公有”对象属性(“stroke”,“strokeWidth”,“angle”等),都可以使用getWidth或getScaleX等方法。 194 | 195 | 您可能会注意到,在早期的示例中,对象在实例化的时候,我们直接传入的配置参数,而上边的例子我们才实例化对象的时候没有传入配置,而是使用的```set```方法传递配置。这是因为它是完全一样的。您可以在创建时“配置”对象,也可以使用```set```方法: 196 | 197 | ```js 198 | var rect = new fabric.Rect({ width: 10, height: 20, fill: '#f55', opacity: 0.7 }); 199 | 200 | // 或者这样 201 | 202 | var rect = new fabric.Rect(); 203 | rect.set({ width: 10, height: 20, fill: '#f55', opacity: 0.7 }); 204 | ``` 205 | 206 | ### 默认设置 207 | 208 | 关于这一点,您可能会问,当我们创建对象而不传递任何“配置”对象时会发生什么。它还有这些属性吗? 209 | 210 | 当然是。 Fabric 中的对象总是具有一组默认的属性值。 211 | 在创建过程中参数未被传递时会被设置成默认的参数。我们可以自己试试看看: 212 | 213 | ```js 214 | var rect = new fabric.Rect(); // 注意没有传递参数 215 | 216 | rect.get('width'); // 0 217 | rect.get('height'); // 0 218 | 219 | rect.get('left'); // 0 220 | rect.get('top'); // 0 221 | 222 | rect.get('fill'); // rgb(0,0,0) 223 | rect.get('stroke'); // null 224 | 225 | rect.get('opacity'); // 1 226 | ``` 227 | 228 | 我们的矩形有一组默认的属性。它位于 0,0,黑色,完全不透明,没有描边,**没有尺寸**(宽度和高度为0)。由于没有尺寸,我们无法在画布上看到它。但是给它宽度/高度的任何正值肯定会在画布的左上角显示一个黑色矩形。 229 | 230 | ![ ](http://fabricjs.com/article_assets/6.png) 231 | 232 | ### 层次和继承 233 | 234 | Fabric对象不仅彼此独立存在。它们形成一个非常精确的层次。 235 | 236 | 大多数对象从根```fabric.Object```继承。```fabric.Object```几乎代表二维形状,位于二维canvas平面,它是一个具有left / top和width / height属性的实体,以及一些其他图形特征。我们在物体上看到的那些属性:fill,stroke,angle,opacity,flip[X|Y]等,对于从```fabric.Object```继承的所有Fabric对象都是通用的。 237 | 238 | 这个继承允许我们在```fabric.Object```上定义方法,并在所有的“类”之间共享它们。例如,如果您想在所有对象上使用```getAngleInRadians```方法,您只需在```fabric.Object.prototype```上创建它即可: 239 | 240 | ```js 241 | fabric.Object.prototype.getAngleInRadians = function() { 242 | return this.get('angle') / 180 * Math.PI; 243 | }; 244 | 245 | var rect = new fabric.Rect({ angle: 45 }); 246 | rect.getAngleInRadians(); // 0.785... 247 | 248 | var circle = new fabric.Circle({ angle: 30, radius: 10 }); 249 | circle.getAngleInRadians(); // 0.523... 250 | 251 | circle instanceof fabric.Circle; // true 252 | circle instanceof fabric.Object; // true 253 | ``` 254 | 255 | 您可以看到,方法立即在所有实例上可用。 256 | 257 | 虽然子类“class”继承自```fabric.Object```,但它们通常也定义自己的方法和属性。例如,```fabric.Circle```需要有“radius”属性。而```Fabric.Image```(我们稍后会看),需要使用```getElement``` / ```setElement```方法来访问/设置图像实例的真实HTML``````元素。 258 | 259 | 使用原型来获取自定义渲染和行为对于高级项目来说是非常普遍的。 260 | 261 | ## Canvas 262 | 263 | 现在我们更详细地讨论了对象,让我们回到 canvas。 264 | 265 | 首先你可以看到所有的Fabric例子如果创建 canvas 对象:```new fabric.Canvas('...')```。```fabric.Canvas```作为围绕``````元素的包装器,并负责管理该canvas上的所有Fabric对象。它需要一个元素的id,并返回一个```fabric.Canvas```的实例。 266 | 267 | 我们可以```add```对象,引用它们,或者```remove```它们: 268 | 269 | ```js 270 | var canvas = new fabric.Canvas('c'); 271 | var rect = new fabric.Rect(); 272 | 273 | canvas.add(rect); // 添加对象 274 | 275 | canvas.item(0); // 源于刚刚添加的第一个矩形对象 276 | canvas.getObjects(); // 获取所有对象(只有一个矩形) 277 | 278 | canvas.remove(rect); // 移除这个矩形 279 | ``` 280 | 281 | 尽管管理对象是```fabric.Canvas```的主要目的。其实它本身也是**可配置**的,需要为整个画布设置背景颜色或图像?将所有内容剪切到某个区域?设置不同的宽度/高度?指定画布是否互动?所有这些选项(和其他)可以在```fabric.Canvas```上设置,无论是在创建时还是之后: 282 | 283 | ```js 284 | var canvas = new fabric.Canvas('c', { 285 | backgroundColor: 'rgb(100,100,200)', 286 | selectionColor: 'blue', 287 | selectionLineWidth: 2 288 | // ... 289 | }); 290 | 291 | // 或者 292 | 293 | var canvas = new fabric.Canvas('c'); 294 | canvas.setBackgroundImage('http://...'); 295 | canvas.onFpsUpdate = function(){ /* ... */ }; 296 | // ... 297 | ``` 298 | 299 | ### 互动 300 | 301 | 虽然我们是一个canvas元素的主题,我们来谈谈互动。Fabric的独特功能之一,在我们刚刚看到的所有的对象模型之上,是一层交互性。 302 | 303 | 存在对象模型以允许编程访问和操纵画布上的对象。但在外部,在用户层面上,有一种方式可以通过鼠标(或触摸,触摸设备)来操纵这些对象。一旦您通过```new fabric.Canvas('...')```初始化画布,可以选择对象,拖动它们,缩放或旋转它们,甚至组合在一起操纵一个**组合**! 304 | 305 | ![ ](http://fabricjs.com/article_assets/7.png) 306 | ![ ](http://fabricjs.com/article_assets/8.png) 307 | 308 | 如果我们希望允许用户在画布上拖动某些东西,比如一个图片,我们需要做的就是初始化画布并在其上添加一个对象。不需要额外的配置或设置。 309 | 310 | 为了控制这种交互性,我们可以在画布上使用 Fabric 的 `selection` 布尔属性,结合各个对象的 `selectable` 布尔属性来使用。 311 | 312 | ```js 313 | var canvas = new fabric.Canvas('c'); 314 | ... 315 | canvas.selection = false; // 禁止所有选中 316 | rect.set('selectable', false); // 只是禁止这个矩形选中 317 | ``` 318 | 319 | 但是如果根本不想要这样的互动呢?如果是这样,您可以随时用```fabric.StaticCanvas```替换```fabric.Canvas```。初始化的语法是相同的;初始化时使用```StaticCanvas```而不是```Canvas```。 320 | 321 | ```js 322 | var staticCanvas = new fabric.StaticCanvas('c'); 323 | 324 | staticCanvas.add( 325 | new fabric.Rect({ 326 | width: 10, height: 20, 327 | left: 100, top: 100, 328 | fill: 'yellow', 329 | angle: 30 330 | })); 331 | ``` 332 | 333 | 这将创建一个“更轻”的画布版本,没有任何事件处理逻辑。请注意,您仍然有一个完整的对象模型来使用:添加对象,删除或修改它们,以及更改任何画布配置,所有这一切仍然有效。这只是事件处理没有了。 334 | 335 | 当我们浏览自定义构建选项时,您会看到,如果```StaticCanvas```是您需要的选项,您甚至可以创建一个较轻的Fabric版本。如果您需要非交互式图表,或者在应用程序中使用过滤器的非交互式图像,这可能是一个不错的选择。 336 | 337 | ## 图像 338 | 339 | 说到图像... 340 | 341 | 在画布上添加矩形和圆圈很有趣,但为什么我们不玩某些图像?正如你现在想象的那样,Fabric 使这个很容易。我们来实例化```fabric.Image```对象并将其添加到画布中: 342 | 343 | ```html 344 | 345 | 346 | ``` 347 | 348 | ```js 349 | var canvas = new fabric.Canvas('c'); 350 | var imgElement = document.getElementById('my-image'); 351 | var imgInstance = new fabric.Image(imgElement, { 352 | left: 100, 353 | top: 100, 354 | angle: 30, 355 | opacity: 0.85 356 | }); 357 | canvas.add(imgInstance); 358 | ``` 359 | 360 | 注意我们如何将图像元素传递给```fabric.Image```构造函数。这将创建一个```fabric.Image```的实例,就像文档中的图像一样。此外,我们立即将左/上值设置为100/100,角度为30,不透明度为0.85。一旦添加到画布中,图像呈现在100,100位置,30度角,并且稍微透明!不错。 361 | 362 | ![ ](http://fabricjs.com/article_assets/9.png) 363 | 364 | 现在,如果我们在文档中真的没有图像,而只是一个图像的URL呢?我们来看看如何使用```fabric.Image.fromURL```: 365 | 366 | ```js 367 | fabric.Image.fromURL('my_image.png', function(oImg) { 368 | canvas.add(oImg); 369 | }); 370 | ``` 371 | 372 | 看起来很简单,不是吗?只需调用具有图像URL的```fabric.Image.fromURL```,并在加载和创建图像后给它一个回调函数来调用。回调函数接收已经创建的```fabric.Image```对象作为第一个参数。此时,您可以将其添加到画布中,也可以先更改,然后添加到画布: 373 | 374 | ```js 375 | fabric.Image.fromURL('my_image.png', function(oImg) { 376 | // 将其缩小,然后将其翻转,然后再将其添加到画布上 377 | oImg.scale(0.5).set('flipX, true); 378 | canvas.add(oImg); 379 | }); 380 | ``` 381 | 382 | ## 路径(Paths) 383 | 384 | 我们已经看过简单的形状,然后看了图像。那么更复杂,丰富的形状和内容呢? 385 | 386 | 认识更强大的搭档:路径(Path)和组合(Groups) 387 | 388 | Fabric 中的 paths 表示可以填充,描边和修改的形状的轮廓。Paths 由一系列命令组成,基本上模仿了从一个点到另一个点的笔。借助“move”,“line”,“curve”或“arc”等命令,path可以形成令人难以置信的复杂形状。在Paths(路径组合)团队的帮助下,可能性更大。 389 | 390 | Fabric 中的路径与 [SVG 元素](http://www.w3.org/TR/SVG/paths.html#PathElement) 非常相似。它们使用相同的命令,可以从``````元素创建,并将其序列化。稍后我们将更加仔细地观察序列化和SVG解析,但现在值得一提的是,您很可能很少手动创建Path实例,相反,您将使用Fabric的内置SVG解析器。但是要了解Path对象是什么,我们来尝试用手创建一个简单的对象: 391 | 392 | ```js 393 | var canvas = new fabric.Canvas('c'); 394 | var path = new fabric.Path('M 0 0 L 200 100 L 170 200 z'); 395 | path.set({ left: 120, top: 120 }); 396 | canvas.add(path); 397 | ``` 398 | 399 | ![ ](http://fabricjs.com/article_assets/10.png) 400 | 401 | 我们通过传递一串路径指令,实例化```fabric.Path```对象,虽然看起来很神秘,但实际上很容易理解。“M”代表“move”命令,并告诉笔移动到```0,0```坐标。“L”表示“line”,笔画线为```200,100```坐标。然后,另一个“L”创建一个连接```170,200```坐标的线段。最后,“z”指示绘画笔关闭当前路径并完成形状。结果,我们得到一个三角形。 402 | 403 | 由于```fabric.Path```与Fabric中的任何其他对象一样,我们还可以更改其某些属性。但是我们可以修改更多: 404 | 405 | ```js 406 | ... 407 | var path = new fabric.Path('M 0 0 L 300 100 L 200 300 z'); 408 | ... 409 | path.set({ fill: 'red', stroke: 'green', opacity: 0.5 }); 410 | canvas.add(path); 411 | ``` 412 | 413 | ![ ](http://fabricjs.com/article_assets/11.png) 414 | 415 | 出于好奇,我们来看一下稍微更复杂的path语法。你会看到为什么手动创建路径可能不是最好的主意。 416 | 417 | ```js 418 | ... 419 | var path = new fabric.Path('M121.32,0L44.58,0C36.67,0,29.5,3.22,24.31,8.41\ 420 | 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,\ 421 | 0,7.48,0C36.66,72.78,8.4,101.04,8.4,101.04C2.98,106.45,0,113.66,0,121.32\ 422 | 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\ 423 | -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,\ 424 | 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\ 425 | -20.28l0-76.74c0-7.66-2.98-14.87-8.4-20.29C136.19,2.98,128.98,0,121.32,0z'); 426 | 427 | canvas.add(path.set({ left: 100, top: 200 })); 428 | ``` 429 | 430 | 看一下这里发生了什么? 431 | 432 | 那么“M”仍然代表“move”的命令,所以笔在“121.32,0”点开始绘画。然后有“L”命令使其为“44.58,0”。到现在为止还可以理解。下一步是什么? “C”指令,代表“cubic bezier”(贝塞尔曲线)。它使笔从当前点绘制贝塞尔曲线到“36.67,0”,它以“29.5,3.22”为起点的控制点,“24.31,8.41”为终点的控制点。这整个事情之后是十几个其他的“cubic bezier”命令,最终创建一个漂亮的箭头形状。 433 | 434 | ![ ](http://fabricjs.com/article_assets/12.png) 435 | 436 | 不过你可能永远不会使用这样复杂的命令,相反,您可能需要使用像```fabric.loadSVGFromString```或```fabric.loadSVGFromURL```方法来加载整个SVG文件,然后让Fabric的SVG解析器完成对所有SVG元素的遍历和创建相应的Path对象的工作。 437 | 438 | 谈到整个SVG文件,而Fabric的路径通常表示SVG``````元素,SVG文档中通常存在的路径集合表示为Group(```fabric.Group```实例)。你可以想像,Group(组合)只不过是一组Path实例和其他对象。而且由于```fabric.Group```从```fabric.Object```继承,它可以像任何其他对象一样添加到画布中,并以相同的方式进行操作。 439 | 440 | 就像Path一样,你可能不会直接的使用它。但是,如果您在解析SVG文档之后偶然发现了一个问题,那么您将确切地知道它是什么以及它的目的是什么。 441 | 442 | ## 后记 443 | 444 | 我们只触及了Fabric的表面。你现在可以很容易地创建任何一个简单的形状,复杂的形状,图像;将它们添加到画布中,并以任何你想要的方式进行修改:位置、尺寸、角度、颜色、笔画、不透明度等。 445 | 446 | 在本系列的下一部分,我们将看看组合,动画,文本,SVG解析,渲染,序列化,事件,图像,滤镜等。 447 | 448 | 与此同时,你可以随意查看带注释的[演示](http://fabricjs.com/demos/)或[基准](http://fabricjs.com/benchmarks/),加入[Google小组](https://groups.google.com/forum/?fromgroups#!forum/fabricjs)或[其他地方](http://stackoverflow.com/questions/tagged/fabricjs)的讨论,或者直接访问[docs](http://fabricjs.com/docs/)、[wiki](https://github.com/kangax/fabric.js/wiki)和[源代码](https://github.com/kangax/fabric.js)。 449 | 450 | 做一些有趣的实验!我希望你喜欢这次旅行。 -------------------------------------------------------------------------------- /part-2.md: -------------------------------------------------------------------------------- 1 | # Fabric.js介绍 第二部分 2 | 3 | 在本系列的第一部分,我们只开始熟悉Fabric.js。我们研究了Fabric在其对象模型和对象层次结构中使用Fabric的各种实例:简单的形状,图像和复杂的路径。我们还学习了如何使用画布上的Fabric对象执行简单的操作。 4 | 5 | 既然基础知识都差不多了,让我们开始学习一些有趣的东西吧! 6 | 7 | ## 动画(animate) 8 | 9 | 没有听说过哪一个强大的canvas库是没有动画功能的,当然Fabric也不例外。既然有这样一个强大的对象模型和图形功能,那么没有内置动画简直就是一种耻辱。 10 | 11 | 改变任何对象的属性是多么容易?我们调用```set```方法,传递相应的值: 12 | 13 | ```js 14 | rect.set('angle', 45); 15 | ``` 16 | 17 | 那么,设置动画对象就是一样的简单。每个Fabric对象都有```animate```方法,它可以让一级对象执行动画。 18 | 19 | ```js 20 | rect.animate('angle', 45, { 21 | onChange: canvas.renderAll.bind(canvas) 22 | }); 23 | ``` 24 | 25 | 第一个参数是动画的属性,第二个参数是动画的最终位置,如果矩形为-15°角,我们传递45,这个矩形将从-15°到45°执行动画。第三个参数是一个可选的对象,指定动画的细节:持续时间,回调,动效等。 26 | 27 | ```animate```的一个方便之处在于它还支持相对值。例如,如果要将对象向左移动100px,则可以这样做: 28 | 29 | ```js 30 | rect.animate('left', '+=100', { onChange: canvas.renderAll.bind(canvas) }); 31 | ``` 32 | 33 | 同样的,逆时针旋转5度,可以像这样完成: 34 | 35 | ```js 36 | rect.animate('angle', '-=5', { onChange: canvas.renderAll.bind(canvas) }); 37 | ``` 38 | 39 | 你可能会想知道为什么我们总是在那里指定“onChange”回调。不是说第三个参数是可选的吗?是为了每个动画帧的渲染都能让我们看到实际的动画效果!您可以看到,当我们调用```animate```方法时,它只会随着时间的推移赋予属性值,遵循特定的算法(例如动效)。所以```rect.animate('angle',45)```会改变对象的角度,但不会在每次角度变化后重新渲染画布屏幕。我们显然需要重新渲染才能看到实际的动画。 40 | 41 | 记住,在画布的下面有整个对象模型。对象有自己的属性和关系,而canvas只负责将它们的存在投射到外部世界。 42 | 43 | 每次更改之后,```animate```处于性能原因,不会自动重新绘制画布。毕竟,我们可以在画布上有数以千计的动画对象,如果每个对象都尝试重新渲染屏幕,这是非常不妥的。在许多对象的情况下,您可以使用像```requestAnimationFrame```(或其他基于定时器的)循环来独立地渲染画布,而不需要为每个对象调用```renderAll```。但大多数情况下,您可能需要将```canvas.renderAll```显式指定为“onChange”的回调。 44 | 45 | 那么我们可以通过哪些其他选项来动画? 46 | 47 | - **from** 允许指定动画属性的起始值(如果我们不希望使用当前值)。 48 | - **duration** 默认为500ms。可以用来改变动画的持续时间。 49 | - **onComplete** 动画结束之后的回调。 50 | - **easing** 动效函数。 51 | 52 | 除了easing,所有这些参数都是见文知意的。我们来看看吧。 53 | 54 | 默认情况下,动画使用“easeInSine”动效执行。如果这不是你需要的,那么在```fabric.util.ease```下有一大堆动效的选项。例如,如果我们想以一种有弹性的方式将对象移到左边: 55 | 56 | ```js 57 | rect.animate('left', 500, { 58 | onChange: canvas.renderAll.bind(canvas), 59 | duration: 1000, 60 | easing: fabric.util.ease.easeOutBounce 61 | }); 62 | ``` 63 | 64 | 注意使用了```fabric.util.ease.easeOutBounce```作为easing的值,其他值得注意的还包括```easeInCubic```,```easeOutCubic```,```easeInElastic```,```easeOutElastic```,```easeInBounce```和```easeOutExpo```。 65 | 66 | 所以这覆盖了Fabric的动画部分。只是给你一些可能的想法:使对象的angle改变来形成旋转动画,使对象的top/left改变来形成移动的动画,使对象的width/height改变来形成大小缩放的动画,等等。 67 | 68 | ## 图像滤镜(filters) 69 | 70 | 在第一节中我们学习了在Fabric中如何使用图像:```fabric.Image```构造函数,它接受图像元素。还有```fabric.Image.fromURL```方法,可以创建URL字符串的图像实例。并且这些图像可以像其他对象一样投射到canvas画布上。 71 | 72 | 但是,像图像这种有趣的东西,对他们应用图像滤镜,效果会更加炫酷! 73 | 74 | Fabric在默认情况下提供了很少的滤镜,无论是否支持WEBGL的浏览器,都可以使用,也很容易去定义。一些你可能非常熟悉的内置的滤镜:去除白色背景、灰度滤镜、反色或亮度调色。其他的则可能不太受欢迎: colormatrix,sepia,noise。 75 | 76 | 那么我们怎么在Fabric中使用滤镜呢?```fabric.Image```的每个实例都有“filters”属性,它是一个简单的滤镜数组。该数组中的每个滤镜都是一个Fabric滤镜的一个实例,或您自己的自定义滤镜的一个实例。 77 | 78 | 让我们创建一个灰度(Grayscale)图像: 79 | 80 | ```js 81 | fabric.Image.fromURL('pug.jpg', function(img) { 82 | // 添加滤镜 83 | img.filters.push(new fabric.Image.filters.Grayscale()); 84 | // 图片加载完成之后,应用滤镜效果 85 | img.applyFilters(); 86 | // 将处理后的图片添加到canvas画布上 87 | canvas.add(img); 88 | }); 89 | ``` 90 | 91 | ![ ](http://fabricjs.com/article_assets/2_1.png) 92 | 93 | 图像的色偏(Sepia)版本怎么样? 94 | 95 | ```js 96 | fabric.Image.fromURL('pug.jpg', function(img) { 97 | img.filters.push(new fabric.Image.filters.Sepia()); 98 | img.applyFilters(); 99 | canvas.add(img); 100 | }); 101 | ``` 102 | 103 | ![ ](http://fabricjs.com/article_assets/2_2.png) 104 | 105 | 由于“filters”属性是一个数组,我们可以用数组方法执行任何所需的操作:移除滤镜(pop,splice,shift),添加滤镜(push,unshift,splice),甚至可以组合多个滤镜。当我们调用```applyFilters```时,“filters”数组中存在的任何滤镜将逐个应用,所以让我们尝试创建一个既色偏又明亮(Brightness)的图像。 106 | 107 | ```js 108 | fabric.Image.fromURL('pug.jpg', function(img) { 109 | img.filters.push( 110 | new fabric.Image.filters.Sepia(), 111 | new fabric.Image.filters.Brightness({ brightness: 100 })); 112 | 113 | img.applyFilters(); 114 | canvas.add(img); 115 | }); 116 | ``` 117 | 118 | ![ ](http://fabricjs.com/article_assets/2_3.png) 119 | 120 | 请注意,我们还将```{brightness:100}```对象传递给Brightness滤镜。这是因为有些滤镜不可配置,例如,灰度,反转,棕褐色(e.g. grayscale, invert, sepia),而其他过滤器可以配置。对于Brightness滤镜,能够配置亮度级别(-1 - 1 即: 全黑色 - 全白色)。对于噪声(noise)滤镜,能配置的是噪声值(0-1000)。对于“删除颜色”滤镜,能配置的是阈值和距离值。等等。 121 | 122 | 那么现在,您已经熟悉了Fabric滤镜,现在是时候开箱并创建自己的滤镜了! 123 | 124 | ```js 125 | fabric.Image.filters.Redify = fabric.util.createClass(fabric.Image.filters.BaseFilter, { 126 | 127 | type: 'Redify', 128 | 129 | /** 130 | * 用于编程的片段源码 131 | */ 132 | fragmentSource: 'precision highp float;\n' + 133 | 'uniform sampler2D uTexture;\n' + 134 | 'varying vec2 vTexCoord;\n' + 135 | 'void main() {\n' + 136 | 'vec4 color = texture2D(uTexture, vTexCoord);\n' + 137 | 'color.g = 0.0;\n' + 138 | 'color.b = 0.0;\n' + 139 | 'gl_FragColor = color;\n' + 140 | '}', 141 | 142 | applyTo2d: function(options) { 143 | var imageData = options.imageData, 144 | data = imageData.data, i, len = data.length; 145 | 146 | for (i = 0; i < len; i += 4) { 147 | data[i + 1] = 0; 148 | data[i + 2] = 0; 149 | } 150 | 151 | } 152 | }); 153 | 154 | fabric.Image.filters.Redify.fromObject = fabric.Image.filters.BaseFilter.fromObject; 155 | ``` 156 | 157 | ![ ](http://fabricjs.com/article_assets/2_4.png) 158 | 159 | 深入研究这段代码,主要的操作是在循环中进行。我们把绿色(data[i+1])和蓝色(data[i+2])替换为0,本质上是移除它们。标准rgb三元色的红色组件保持不变,基本上使整个图像变成红色。如您所见,```applyTo```方法传递一个选项对象,该对象包含滤镜管道的该阶段的图像的数据```imageData```。从这里,我们可以遍历其像素(getImageData().data),以任何我们想要的方式修改它们。如果要浏览器WEBGL启用的滤镜能够在GPU上运行,要做到这一点,你必须提供一个片段着色器来描述在像素上执行的操作。在Fabric中定义了许多滤镜,您可以在其中看到如何编写片段或着色器的示例。 160 | 161 | ## 颜色(Colors) 162 | 163 | 无论您是否使用十六进制,RGB或RGBA颜色,Fabric都能提供一个完整的颜色基础,以帮助您最自然地表达自己。以下是在Fabric中定义颜色的一些方法: 164 | 165 | ```js 166 | new fabric.Color('#f55'); 167 | new fabric.Color('#123123'); 168 | new fabric.Color('356735'); 169 | new fabric.Color('rgb(100,0,100)'); 170 | new fabric.Color('rgba(10, 20, 30, 0.5)'); 171 | ``` 172 | 173 | 转换也很简单。 ```toHex()```将颜色实例转换为十六进制表示。 ```toRgb()```可以转换为RGB,```toRgba()```转换为带Alpha通道的RGB。 174 | 175 | ```js 176 | new fabric.Color('#f55').toRgb(); // "rgb(255,85,85)" 177 | new fabric.Color('rgb(100,100,100)').toHex(); // "646464" 178 | new fabric.Color('fff').toHex(); // "FFFFFF" 179 | ``` 180 | 181 | 转换不仅仅可以使单一的颜色。您也可以用另一种颜色叠加,或将其转换为灰度版本。 182 | 183 | ```js 184 | var redish = new fabric.Color('#f55'); 185 | var greenish = new fabric.Color('#5f5'); 186 | 187 | redish.overlayWith(greenish).toHex(); // "AAAA55" 188 | redish.toGrayscale().toHex(); // "A1A1A1" 189 | ``` 190 | 191 | ## 渐变(Gradients) 192 | 193 | 更加表现出色的方式是通过渐变。渐变让我们将一种颜色混合到另一种颜色中,创造出一些令人惊叹的图形效果。 194 | 195 | Fabric通过```setGradient```方法支持渐变,在所有对象上定义。调用```setGradient('fill', { ... })```就像设置一个对象的“fill”值一样,只不过我们用渐变而不是单色来填充对象。 196 | 197 | ```js 198 | var circle = new fabric.Circle({ 199 | left: 100, 200 | top: 100, 201 | radius: 50 202 | }); 203 | 204 | var gradient = new fabric.Gradient({ 205 | type: 'linear', 206 | gradientUnits: 'pixels', // or 'percentage' 207 | coords: { x1: 0, y1: 0, x2: 0, y2: circle.height }, 208 | colorStops:[ 209 | { offset: 0, color: '#000' }, 210 | { offset: 1, color: '#fff'} 211 | ] 212 | }) 213 | 214 | circle.set('fill', gradient); 215 | ``` 216 | 217 | ![ ](http://fabricjs.com/article_assets/2_15.png) 218 | 219 | 在上面的例子中,我们在100,100的位置创建一个圆圈,半径为50px。然后,我们将其填充设置为跨越整个圆形高度的渐变,从黑色到白色。 220 | 221 | 传递给一个方法的参数是一个options对象,它需要2个坐标对(x1,y1和x2,y2)以及“colorStops”对象。坐标指出了渐变开始和结束的位置,```colorStops```指出了渐变的颜色,只要它们的范围从0到1(例如0,0.1,0.3,0.5,0.75,1),您可以根据需要定义多少个```colorStops```。0表示渐变的开始,1表示结束。 222 | 223 | 坐标相对于物体左上角,因此圆的最高点为0,最低点为```circle.height```。 ```setGradient```以相同的方式计算宽度坐标(x1,x2)。 224 | 225 | 这是一个从左到右的红蓝渐变: 226 | 227 | ```js 228 | circle.setGradient('fill', { 229 | x1: 0, 230 | y1: 0, 231 | x2: circle.width, 232 | y2: 0, 233 | colorStops: { 234 | 0: "red", 235 | 1: "blue" 236 | } 237 | }); 238 | ``` 239 | 240 | ![ ](http://fabricjs.com/article_assets/2_16.png) 241 | 242 | 这是一个彩虹🌈渐变,颜色跨度20%: 243 | 244 | ```js 245 | circle.setGradient('fill', { 246 | x1: 0, 247 | y1: 0, 248 | x2: circle.width, 249 | y2: 0, 250 | colorStops: { 251 | 0: "red", 252 | 0.2: "orange", 253 | 0.4: "yellow", 254 | 0.6: "green", 255 | 0.8: "blue", 256 | 1: "purple" 257 | } 258 | }); 259 | ``` 260 | 261 | ![ ](http://fabricjs.com/article_assets/2_17.png) 262 | 263 | 你还能想到哪些炫酷的版本? 264 | 265 | ## 文本(Text) 266 | 267 | 如果你想不仅在画布上显示图像和矢量形状,还要显示文本,该怎么办?Fabric也为您准备好了!认识```fabric.Text```对象。 268 | 269 | 我们提供文本的原因有两个,首先是允许以面向对象的方式处理文本。原生canvas方法,只允许在非常低的级别上填充或描边文本。通过实例化```fabric.Text```实例,我们可以使用文本,就像我们将使用任何其他Fabric对象:移动它,缩放它,更改其属性等。 270 | 271 | 第二个原因是提供比canvas给我们更丰富的功能,包括: 272 | 273 | - **支持多行Multiline support** 不幸的是,原生文本方法忽略了新建一行。 274 | - **文本对齐Text alignment** 左,中,右。使用多行文本时很有用。 275 | - **文本背景Text background** 背景也支持文本对齐。 276 | - **文字装饰Text decoration** 下划线,上划线,贯穿线。 277 | - **行高Line Height** 在使用多行文本时有用。 278 | - **字符间距Char spacing** 使文本更紧凑或更间隔。 279 | - **子范围Subranges** 将颜色和属性应用到文本对象的子对象中。 280 | - **多字节Multibyte** 支持表情符号。 281 | - **交互式画布编辑On canvas editing** 可以直接在画布上键入文本。 282 | 283 | 做一个hello world的例子: 284 | 285 | ```js 286 | var text = new fabric.Text('hello world', { left: 100, top: 100 }); 287 | canvas.add(text); 288 | ``` 289 | 290 | 那就对了!在画布上显示文本就像在所需位置添加```fabric.Text```实例一样简单。您可以看到,唯一必需的第一个参数是实际的文本字符串。第二个参数是通常的选项对象,它可以具有通常的left,top,填fill,opacity等属性。 291 | 292 | 当然,文本对象也有自己独有的与文本相关的属性。我们来看看其中的一些: 293 | 294 | ### 字体(fontFamily) 295 | 296 | 默认设置为“Times New Roman”,此属性允许我们更改用于呈现文本对象的字体系列。更改它将立即使文本以新字体呈现。 297 | 298 | ```js 299 | var comicSansText = new fabric.Text("I'm in Comic Sans", { 300 | fontFamily: 'Comic Sans' 301 | }); 302 | ``` 303 | 304 | ![ ](http://fabricjs.com/article_assets/2_5.png) 305 | 306 | ### 字体大小(fontSize) 307 | 308 | 字体大小控制渲染文本的大小。请注意,与Fabric中的其他对象不同,您不能直接更改文本的width / height属性。相反,您需要更改“fontSize”值才能使文本对象更大或更小。或者,您可以随时使用scaleX / scaleY属性。 309 | 310 | ```js 311 | var text40 = new fabric.Text("I'm at fontSize 40", { 312 | fontSize: 40 313 | }); 314 | var text20 = new fabric.Text("I'm at fontSize 20", { 315 | fontSize: 20 316 | }); 317 | ``` 318 | 319 | ![ ](http://fabricjs.com/article_assets/2_6.png) 320 | 321 | ### 粗体(fontWeight) 322 | 323 | ```fontWeight```允许使文本更粗或更薄。就像CSS一样,您可以使用关键字(“normal”,“bold”)或数字(100,200,400,600,800)。请注意,您是否可以使用某些值取决于所选字体的粗体的可用性。如果您使用远程字体,则需要确保提供正常和粗体字体定义。 324 | 325 | ```js 326 | var normalText = new fabric.Text("I'm a normal text", { 327 | fontWeight: 'normal' 328 | }); 329 | var boldText = new fabric.Text("I'm at bold text", { 330 | fontWeight: 'bold' 331 | }); 332 | ``` 333 | 334 | ![ ](http://fabricjs.com/article_assets/2_7.png) 335 | 336 | ### 文本修饰(textDecoration) 337 | 338 | 文本修饰允许添加下划线,上划线或贯穿线到文本。这与CSS类似,但是Fabric进一步,并允许使用上述的任何组合。所以你可以有一个文本,既有下划线又有上划线,还有贯穿线。等等。 339 | 340 | ```js 341 | var underlineText = new fabric.Text("I'm an underlined text", { 342 | underline; true 343 | }); 344 | var strokeThroughText = new fabric.Text("I'm a stroke-through text", { 345 | linethrough: true 346 | }); 347 | var overlineText = new fabric.Text("I'm an overline text", { 348 | overline: true 349 | }); 350 | ``` 351 | 352 | ![ ](http://fabricjs.com/article_assets/2_8.png) 353 | 354 | ### 阴影 355 | 356 | 此属性在版本1.3.0之前被称为“textShadow” 357 | 358 | 文本阴影由4个组件组成:颜色,水平偏移,垂直偏移和模糊大小。如果您在CSS中使用阴影,这可能看起来很熟悉。通过更改这些值可以实现许多组合。 359 | 360 | ```js 361 | var shadowText1 = new fabric.Text("I'm a text with shadow", { 362 | shadow: 'rgba(0,0,0,0.3) 5px 5px 5px' 363 | }); 364 | var shadowText2 = new fabric.Text("And another shadow", { 365 | shadow: 'rgba(0,0,0,0.2) 0 0 5px' 366 | }); 367 | var shadowText3 = new fabric.Text("Lorem ipsum dolor sit", { 368 | shadow: 'green -5px -5px 3px' 369 | }); 370 | ``` 371 | 372 | ![ ](http://fabricjs.com/article_assets/2_9.png) 373 | 374 | ### 字体风格(fontStyle) 375 | 376 | 字体样式可以是2个值之一:normal(正常)或italic(斜体)。这类似于同名的CSS属性。 377 | 378 | ```js 379 | var italicText = new fabric.Text("A very fancy italic text", { 380 | fontStyle: 'italic', 381 | fontFamily: 'Delicious' 382 | }); 383 | var anotherItalicText = new fabric.Text("another italic text", { 384 | fontStyle: 'italic', 385 | fontFamily: 'Hoefler Text' 386 | }); 387 | ``` 388 | 389 | ![ ](http://fabricjs.com/article_assets/2_10.png) 390 | 391 | ### 描边的颜色和宽度(stroke and strokeWidth) 392 | 393 | 通过结合stroke和strokeWidth,你可以在你的文字上获得一些有趣的效果。这里有几个例子: 394 | 395 | ```js 396 | var textWithStroke = new fabric.Text("Text with a stroke", { 397 | stroke: '#ff1318', 398 | strokeWidth: 1 399 | }); 400 | var loremIpsumDolor = new fabric.Text("Lorem ipsum dolor", { 401 | fontFamily: 'Impact', 402 | stroke: '#c3bfbf', 403 | strokeWidth: 3 404 | }); 405 | ``` 406 | 407 | ![ ](http://fabricjs.com/article_assets/2_11.png) 408 | 409 | 410 | ### 文本对齐(textAlign) 411 | 412 | 使用多行文本时,文本对齐很有用。使用单行文本,边界框的宽度总是与该行的宽度完全匹配,因此没有任何对齐方式。 413 | 414 | 允许的值为“left”,“center”和“right”。 415 | 416 | ```js 417 | var text = 'this is\na multiline\ntext\naligned right!'; 418 | var alignedRightText = new fabric.Text(text, { 419 | textAlign: 'right' 420 | }); 421 | ``` 422 | 423 | ![ ](http://fabricjs.com/article_assets/2_12.png) 424 | 425 | ### 行高(lineHeight) 426 | 427 | CSS中可能熟悉的另一个属性是lineHeight。它允许我们改变多行文本中文本行之间的垂直间距,只是值的规格和css中的不太一样,在以下示例中,第一个文本块的lineHeight为3,第二个为1。 428 | 429 | ```js 430 | var lineHeight3 = new fabric.Text('Lorem ipsum ...', { 431 | lineHeight: 3 432 | }); 433 | var lineHeight1 = new fabric.Text('Lorem ipsum ...', { 434 | lineHeight: 1 435 | }); 436 | ``` 437 | 438 | ![ ](http://fabricjs.com/article_assets/2_13.png) 439 | 440 | ### 文本背景颜色(textBackgroundColor) 441 | 442 | 最后,textBackgroundColor允许给文本一个背景。请注意,背景仅填充文本字符占用的空间,而不是整个边框。这意味着文本对齐改变了文本背景渲染的方式。一行的高度也是如此,因为背景是由lineHeight创建的线之间的垂直空间。 443 | 444 | ```js 445 | var text = 'this is\na multiline\ntext\nwith\ncustom lineheight\n&background'; 446 | var textWithBackground = new fabric.Text(text, { 447 | textBackgroundColor: 'rgb(0,200,0)' 448 | }); 449 | ``` 450 | 451 | ![ ](http://fabricjs.com/article_assets/2_14.png) 452 | 453 | ## 事件(Events) 454 | 455 | 事件驱动架构是框架内一些惊人的功能和灵活性的基础。Fabric也不例外,并提供广泛的事件系统,从低级“鼠标”事件开始到高级对象。 456 | 457 | 这些事件可以让我们在画布上发生的事件的同时,做一些事情。想知道什么时候鼠标被按下?只需要监听“mouse:down”事件,想知道什么时候对象被添加到canvas? 监听“object:add”事件,如果想知道画布什么时候渲染完成呢?只需监听“after:render”。 458 | 459 | 事件API非常简单,类似于jQuery,Underscore.js或其他流行的JS库。有一个```on```方法来初始化事件监听器,```off```用来删除它。 460 | 461 | 让我们来看个例子: 462 | 463 | ```js 464 | var canvas = new fabric.Canvas('...'); 465 | canvas.on('mouse:down', function(options) { 466 | console.log(options.e.clientX, options.e.clientY); 467 | }); 468 | ``` 469 | 470 | 我们将事件“mouse:down”事件侦听器添加到画布上,并给它一个事件处理程序,用于记录事件起始位置的坐标。换句话说,它会记录鼠标按下的位置。事件处理程序接收一个选项对象,它有两个属性```e```:事件源,以及```target```:在画布上点击的对象,它有时不存在。但是这个事件在任何时候都是存在的,但是只有当你在画布上点击某个对象时,```target```才会存在。```target```也只传递给有意义的事件处理程序。例如,会传递给“mouse:down”,而不会传递给“after:render”(表示整个画布被重新绘制)。 471 | 472 | ```js 473 | canvas.on('mouse:down', function(options) { 474 | if (options.target) { 475 | console.log('有对象被点击咯! ', options.target.type); 476 | } 477 | }); 478 | ``` 479 | 480 | 上面的例子将记录“有对象被点击咯!”如果您单击一个对象。它还将显示点击的对象的类型。 481 | 482 | 那么Fabric中有哪些其他的事件呢?那么,从鼠标级别可以看到“mouse:down”,“mouse:move”和“mouse:up”。从通用方面看,有“after:render”。然后有选择相关的事件:“before:selection:cleared”, “selection:created”, “selection:cleared”,最后,对象的事件:“object:modified”,“object:selected”,“object:moving”,“object:scaling”,“object:rotate”,“object:added”和“object:removed”。 483 | 484 | 请注意,每当一个对象被移动(或缩放)甚至一个像素时,诸如“object:moving”(或“object:scaling”)的事件被连续地触发。另一方面,诸如“object:modified”或“selection:created”之类的事件仅在操作结束时被触发(对象修改或选择创建)。 485 | 486 | 注意我们如何将事件附加到画布上(```canvas.on('mouse:down',...)```)。可以想象,这意味着事件都被限定为canvas实例。如果您在页面上有多个canvas,您可以将不同的事件侦听器附加到每个canvas上。他们都是独立的,只处理分配给他们自己的事件。 487 | 488 | 为方便起见,Fabric将进一步提升事件系统,并允许您将侦听器直接附加到canvas画布中的对象上。让我们来看看: 489 | 490 | ```js 491 | var rect = new fabric.Rect({ width: 100, height: 50, fill: 'green' }); 492 | rect.on('selected', function() { 493 | console.log('selected a rectangle'); 494 | }); 495 | 496 | var circle = new fabric.Circle({ radius: 75, fill: 'blue' }); 497 | circle.on('selected', function() { 498 | console.log('selected a circle'); 499 | }); 500 | ``` 501 | 502 | 我们将事件侦听器直接附加到矩形和圆形实例。使用"selected"来代替"object:selected"。同样的,使用“modified”代替“object:modified”,使用“rotating”来代替“object:rotating”。等等。。 503 | 504 | 查看此事件[演示](http://fabricjs.com/events),以更广泛地探索Fabric的事件系统 505 | 506 | -------------------------------------------------------------------------------- /part-3.md: -------------------------------------------------------------------------------- 1 | # Fabric.js介绍 第三部分 2 | 3 | 我们已经介绍了本系列的第一部分和第二部分的大部分基础知识。我们继续前进到更高级的技巧! 4 | 5 | ## 组合(Group) 6 | 7 | ![ ](http://fabricjs.com/article_assets/8.png) 8 | 9 | 我们首先谈论的是组合。组合是Fabric最强大的功能之一。 将任何Fabric对象分组成一个单一实体的简单方法,为什么要这样做?当然是为了能够将这些对象作为一个单元来处理。 10 | 11 | 可以用鼠标来将画布上的任何数量的Fabric对象进行分组,形成单一的组合形式,一旦分组,组内Fabric对象可以一起移动,甚至一起修改。他们组成一个组。我们可以缩放这个组合,旋转,甚至改变其表现性质:颜色、透明度、边界等。 12 | 13 | 这正是组合所要做的,每当您在画布上看到这样的选择时,Fabric将在内部创建一组对象。只有以编程方式提供使用组的访问才有意义。这就是是fabric的组合。 14 | 15 | 让我们创建一个包含两个Fabric对象的组合,圆和文本: 16 | 17 | ```js 18 | var circle = new fabric.Circle({ 19 | radius: 100, 20 | fill: '#eef', 21 | scaleY: 0.5, 22 | originX: 'center', 23 | originY: 'center' 24 | }); 25 | 26 | var text = new fabric.Text('hello world', { 27 | fontSize: 30, 28 | originX: 'center', 29 | originY: 'center' 30 | }); 31 | 32 | var group = new fabric.Group([ circle, text ], { 33 | left: 150, 34 | top: 100, 35 | angle: -10 36 | }); 37 | 38 | canvas.add(group); 39 | ``` 40 | 41 | 首先,我们创建了一个“hello world”文本对象。将```originX```和```originY```设置为“center”会使得将以组合为中心,默认情况下,组合成员相对于组合的左上角定位。然后,圆圈以100px半径填充“#eef”颜色并垂直缩放0.5倍(scaleY = 0.5)。然后,我们创建了一个```fabric.Group```实例,通过一个包含这两个Fabric对象的数组传递给这个组合,并给它的位置为150/100和-10角度。最后,将这个组合添加到画布(使用```canvas.add()```)。 42 | 43 | 您会看到画布上的一个对象,看起来像一个椭圆标签。请注意,如果我们要修改他们的样子,可以将该他们作为单个实体使用,我们只是改变组合的属性,给出要更改的的```left```,```top```和```angle```值。 44 | 45 | ![ ](http://fabricjs.com/article_assets/3_1.png) 46 | 47 | 让我们再来修改一下canvas画布上的这个组合: 48 | 49 | ```js 50 | group.item(0).set('fill', 'red'); 51 | group.item(1).set({ 52 | text: 'trololo', 53 | fill: 'white' 54 | }); 55 | ``` 56 | 57 | 这里发生了什么?我们通过```item()```方法访问组中的各个对象,并修改其属性。第一个对象是椭圆,第二个是文字。让我们看看发生了什么: 58 | 59 | ![ ](http://fabricjs.com/article_assets/3_2.png) 60 | 61 | 您现在可能注意到的一件重要的事情是,组合中的对象都相对于组合的中心定位,当我们改变了text对象中的内容时,即使内容的宽度改变了,但是它仍然保持居中的位置,如果你不想要这个行为,您需要指定对象的left/right坐标。在这种情况下,根据这些坐标将它们分组在一起。 62 | 63 | 让我们创建并组合3个圆,使它们相互水平定位: 64 | 65 | ```js 66 | var circle1 = new fabric.Circle({ 67 | radius: 50, 68 | fill: 'red', 69 | left: 0 70 | }); 71 | var circle2 = new fabric.Circle({ 72 | radius: 50, 73 | fill: 'green', 74 | left: 100 75 | }); 76 | var circle3 = new fabric.Circle({ 77 | radius: 50, 78 | fill: 'blue', 79 | left: 200 80 | }); 81 | 82 | var group = new fabric.Group([ circle1, circle2, circle3 ], { 83 | left: 200, 84 | top: 100 85 | }); 86 | 87 | canvas.add(group); 88 | ``` 89 | 90 | ![ ](http://fabricjs.com/article_assets/3_3.png) 91 | 92 | 使用团体时要注意的另一件事是**对象的状态**,举个例子,当使用图片形成组合时,您需要确保这些图像已经完全加载。由于Fabric已经提供了帮助方法来确保图像被加载,这变得相当容易: 93 | 94 | ```js 95 | fabric.Image.fromURL('/assets/pug.jpg', function(img) { 96 | var img1 = img.scale(0.1).set({ left: 100, top: 100 }); 97 | 98 | fabric.Image.fromURL('/assets/pug.jpg', function(img) { 99 | var img2 = img.scale(0.1).set({ left: 175, top: 175 }); 100 | 101 | fabric.Image.fromURL('/assets/pug.jpg', function(img) { 102 | var img3 = img.scale(0.1).set({ left: 250, top: 250 }); 103 | 104 | canvas.add(new fabric.Group([ img1, img2, img3], { left: 200, top: 200 })) 105 | }); 106 | }); 107 | }); 108 | ``` 109 | 110 | ![ ](http://fabricjs.com/article_assets/3_4.png) 111 | 112 | 在使用组合时可以使用哪些其他方法?使用```getObjects()```方法,可以返回一个包含组合中所有对象的数组,使用```size()```可以知道组合中所有对象的数量。使用```contains()```可以检查某个对象是否在组合中。我们之前看到的```item()```可以检索组中的特定对象。使用```forEachObject```可以遍历每个组合中的对象。还有```add()```和```remove()```方法来相应地从组中添加和删除对象。 113 | 114 | 您可以通过2种方式添加/删除组中的对象:更新组合的尺寸/位置,我们建议使用更新尺寸,除非您正在执行批量处理操作,并且在进程中组合的宽度/高度都没有问题。 115 | 116 | 在组合中心添加一个长方形: 117 | 118 | ```js 119 | group.add(new fabric.Rect({ 120 | ... 121 | originX: 'center', 122 | originY: 'center' 123 | })); 124 | ``` 125 | 126 | 在组合的中心附近100px添加矩形: 127 | 128 | ```js 129 | group.add(new fabric.Rect({ 130 | ... 131 | left: 100, 132 | top: 100, 133 | originX: 'center', 134 | originY: 'center' 135 | })); 136 | ``` 137 | 138 | 在组合中心添加矩形并且更新组合的尺寸: 139 | 140 | ```js 141 | group.addWithUpdate(new fabric.Rect({ 142 | ... 143 | left: group.get('left'), 144 | top: group.get('top'), 145 | originX: 'center', 146 | originY: 'center' 147 | })); 148 | ``` 149 | 150 | 在组合的中心附近100px添加矩形并且更新组合的尺寸: 151 | 152 | ```js 153 | group.addWithUpdate(new fabric.Rect({ 154 | ... 155 | left: group.get('left') + 100, 156 | top: group.get('top') + 100, 157 | originX: 'center', 158 | originY: 'center' 159 | })); 160 | ``` 161 | 162 | 最后,如果您想创建一个已经存在于画布上的对象的组合,则需要首先克隆它们: 163 | 164 | ```js 165 | // 创建一个包含两个已存在对象的副本的组合 166 | var group = new fabric.Group([ 167 | canvas.item(0).clone(), 168 | canvas.item(1).clone() 169 | ]); 170 | 171 | // 移除所有对象并且重新渲染 172 | canvas.clear().renderAll(); 173 | 174 | // 将组合添加到canvas画布 175 | canvas.add(group); 176 | ``` 177 | 178 | ## 序列化 179 | 180 | 一旦你开始构建一种有状态的应用程序,也许允许用户在服务器上保存画布内容的结果,或者将内容流传输到不同的客户端,你都需要**canvas序列化**。如果仍然要发送画布内容,也是可以的,有一个选项可以将画布导出到图像。但是上传图片到服务器无疑是相当占用带宽的,论大小,没有什么可以比得过文本了,这就是为什么Fabric为canvas画布序列化/反序列化提供了极好的支持。 181 | 182 | ### toObject, toJSON 183 | 184 | Fabric中的canvas序列化方法主要是```toObject()```和```toJSON()```方法。我们来看一个简单的例子,首先序列化一个空的画布: 185 | 186 | ```js 187 | var canvas = new fabric.Canvas('c'); 188 | JSON.stringify(canvas); 189 | // '{"objects":[],"background":"rgba(0, 0, 0, 0)"}' 190 | ``` 191 | 192 | 我们使用ES5 ```JSON.stringify()```方法,如果```toJSON```方法存在,则会隐性调用。由于Fabric中的canvas实例已经具有```toJSON```方法,就相当于我们调用了```canvas.toJSON()```。 193 | 194 | 注意返回的表示空的canvas画布的字符串。它是JSON形式,并且由“objects”和“background”属性组成。“objects”当前为空,因为canvas上没有任何内容,而background的默认值为“rgba(0,0,0,0)”)。 195 | 196 | 让我们给画布不同的背景,看看它有什么变化: 197 | 198 | ```js 199 | canvas.backgroundColor = 'red'; 200 | JSON.stringify(canvas); 201 | // '{"objects":[],"background":"red"}' 202 | ``` 203 | 204 | 正如所料,画布表现现在反映出新的背景颜色。现在,我们来添加一些Fabric对象! 205 | 206 | ```js 207 | canvas.add(new fabric.Rect({ 208 | left: 50, 209 | top: 50, 210 | height: 20, 211 | width: 20, 212 | fill: 'green' 213 | })); 214 | console.log(JSON.stringify(canvas)); 215 | ``` 216 | 217 | 输出是: 218 | 219 | ``` 220 | '{"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}],"background":"rgba(0, 0, 0, 0)"}' 221 | ``` 222 | 223 | 看起来改变了好多,在“objects”数组新增了一个对象,序列化为JSON。请注意它的表示方式是如何包含其所有视觉特征:left, top, width, height, fill, stroke等。 224 | 225 | 如果我们要添加另一个对象,比如,一个位于矩形旁边的红色圆圈,您会看到相应改变: 226 | 227 | ```js 228 | canvas.add(new fabric.Circle({ 229 | left: 100, 230 | top: 100, 231 | radius: 50, 232 | fill: 'red' 233 | })); 234 | console.log(JSON.stringify(canvas)); 235 | ``` 236 | 237 | 输出是: 238 | 239 | ``` 240 | '{"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)"}' 241 | ``` 242 | 243 | 注意看```“type”:“rect”```和```“type”:“circle”```部分,即使起初看起来可能会有很多输出,但是与图像序列化所得到的结果无关。只是为了比较,让我们来看看你将用```canvas.toDataURL({format: 'png'})``(译者注:这个命令原版有误)`获得的一个字符串的大约1/10(!) 244 | 245 | ``` 246 | data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAK8CAYAAAAXo9vkAAAgAElEQVR4Xu3dP4xtBbnG4WPAQOQ2YBCLK1qpoQE1/m+NVlCDwUACicRCEuysrOwkwcJgAglEItRQaWz9HxEaolSKtxCJ0FwMRIj32zqFcjm8e868s2fNWo/Jygl+e397rWetk5xf5pyZd13wPwIECBAgQIAAAQIECBxI4F0H+hwfQ4AAAQIECBAgQIAAgQsCxENAgAABAgQIECBAgMDBBATIwah9EAECBAgQIECAAAECAsQzQIAAAQIECBAgQIDAwQQEyMGofRABAgQIECBAgAABAgLEM0CAAAECBAgQIECAwMEEBMjBqH0QAQIECBAgQIAAAQICxDNAgAABAgQIECBAgMDBBATIwah9EAECBAgQIECAAAECAsQzQIAAAQIECBAgQIDAwQQEyMGofRABAgQIECBAgAABAgLEM0CAAAECBAgQIECAwMEEBMjBqH0QAQIECBAgQIAAAQICxDNAgAABAgQIECBAgMDBBATIwah9EAECBAgQIECAAAECAsQzQIAAAQIECBAgQIDAwQQEyMGofRABAgQIECBAgAABAgLEM0CAAAECBAgQIECAwMEEBMjBqH0QAQIECBAgQIAAAQICxDNAgAABAgQIECBAgMDBBATIwah9EAECBAgQIECAAAECAsQzQIAAAQIECBAgQIDAwQQEyMGofRABAgQIECBAgAABAgLEM0CAAAECBAgQIECAwMEEBMjBqH0QAQIECBAgQIAAAQICxDNAgAABAgQIECBAgMDBBATIwah9EAECBAgQIECAAAECAsQzQIAAAQIECBAgQIDAwQQEyMGofRABAgQIECBAgAABAgLEM0CAAAECBAgQIECAwMEEBMjBqH0QAQIECBAgQIAAAQICxDNAgAABAgQIECBAgMDBBATIwah9EAECBAgQIECAAAECAsQzQIAAAQIECBAgQIDAwQQEyMGofRABAgQIECBAgAABAgLEM0CAAAECBAgQIECAwMEEBMjBqH0QAQIECBAgQIAAAQICxDNAgAABAgQIECBAgMDBBATIwah9EAECBAgQIECAAAECyw+Qb134R/U2fevC8q+5esGWESBAgAABAgQIEFiOwPL/MC5AlvO0OBMCBAgQIECAAAECJxQQICcE9HYCBAgQIECAAAECBPYXECD7W3klAQIECBAgQIAAAQInFBAgJwT0dgIECBAgQIAAAQIE9hcQIPtbeSUBAgQIECBAgAABAicUECAnBPR2AgQIECBAgAABAgT2FxAg+1t5JQECBAgQIECAAAECJxQQICcE9HYCBAgQIECAAAECBPYXECD7W3klAQIECBAgQIAAAQInFBAgJwTc9+3z49yvmNd+dI7PzPHJOW6Y4wNzXD3HlXNc9pZdb85/vzbHK3P8aY7n5vj1HL+Y43dz417f97O9jgABAgQIECBAgMBSBATIKd2JCY5dWNwyx5fn+PwcV5U/6tXZ99M5fjjHk3Mjd6HifwQIECBAgAABAgQWLSBAirdnouP6WXfvHHfOcU1x9T6rXp4XPTLHA3NTX9jnDV5DgAABAgQIECBA4NACAuSE4hMdl8+Kr83xzTmuO+G61ttfnEXfnuN7c4PfaC21hwABAgQIECBAgMBJBQTIJQpOeFw7b71/jtsvccWh3vbYfNB9c6NfOtQH+hwCBAgQIECAAAECFxMQIMd8No7C4+F5283HfOtZv/ypOYG7hMhZ3wafT4AAAQIECBDYtoAA2fP+H/1Vqwd3f4jf8y1Lfdkunu7xV7OWenucFwECBAgQIEBg3QICZI/7O/Fxx7xs9wf3t36r3D3evciX7L7F7+6rIY8u8uycFAECBAgQIE 247 | ``` 248 | 249 | 你可能会想知道为什么还有```toObject```。很简单,```toObject```返回与```toJSON```相同的表示形式,只能以实际对象的形式,而不用字符串序列化。例如,以一个绿色矩形的canvas的之前的例子。 250 | 251 | ```canvas.toObject()```的输出是这样的: 252 | 253 | ```json 254 | { "background" : "rgba(0, 0, 0, 0)", 255 | "objects" : [ 256 | { 257 | "angle" : 0, 258 | "fill" : "green", 259 | "flipX" : false, 260 | "flipY" : false, 261 | "hasBorders" : true, 262 | "hasControls" : true, 263 | "hasRotatingPoint" : false, 264 | "height" : 20, 265 | "left" : 50, 266 | "opacity" : 1, 267 | "overlayFill" : null, 268 | "perPixelTargetFind" : false, 269 | "scaleX" : 1, 270 | "scaleY" : 1, 271 | "selectable" : true, 272 | "stroke" : null, 273 | "strokeDashArray" : null, 274 | "strokeWidth" : 1, 275 | "top" : 50, 276 | "transparentCorners" : true, 277 | "type" : "rect", 278 | "width" : 20 279 | } 280 | ] 281 | } 282 | ``` 283 | 284 | 可以看出,```toJSON```输出本质上是一个字符串的```toObject```输出。现在,有趣的(有用的)事情是,toObject输出是聪明和懒惰的。您在“objects”数组中看到的内容是遍历canvas画布上所有的对象并调用该对象本身自己的```toObject```方法的结果。```fabric.Path```有自己的```toObject```,返回路径的“points”数组, 285 | ```fabric.Image```也有自己的```toObject```,返回图像的“src”属性。以真正的面向对象的方式,所有对象都能够自己序列化。 286 | 287 | 这意味着当您创建自己的“类”,或者需要定制对象的序列化表示时,您需要做的就是使用```toObject```方法 - 完全替换它或扩展它。我们来试试一下: 288 | 289 | ```js 290 | var rect = new fabric.Rect(); 291 | rect.toObject = function() { 292 | return { name: 'trololo' }; 293 | }; 294 | canvas.add(rect); 295 | console.log(JSON.stringify(canvas)); 296 | ``` 297 | 298 | 输出: 299 | 300 | ``` 301 | '{"objects":[{"name":"trololo"}],"background":"rgba(0, 0, 0, 0)"}' 302 | ``` 303 | 304 | 您可以看到,objects数组现在有一个自定义的展示。这种覆盖可能不是很有用 - 尽管把重点提到了:我们如何拓展和更改矩形的```toObject```方法与额外的属性。 305 | 306 | ```js 307 | var rect = new fabric.Rect(); 308 | 309 | rect.toObject = (function(toObject) { 310 | return function() { 311 | return fabric.util.object.extend(toObject.call(this), { 312 | name: this.name 313 | }); 314 | }; 315 | })(rect.toObject); 316 | 317 | canvas.add(rect); 318 | 319 | rect.name = 'trololo'; 320 | 321 | console.log(JSON.stringify(canvas)); 322 | ``` 323 | 324 | 输出: 325 | 326 | ``` 327 | '{"objects":[{"type":"rect","left":0,"top":0,"width":0,"height":0,"fill":"rgb(0,0,0)","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,"name":"trololo"}],"background":"rgba(0, 0, 0, 0)"}' 328 | ``` 329 | 330 | 我们使用附加属性“name”来扩展对象的现有```toObject```方法,所以“name”属性是```toObject```输出的一部分,值得一提的还有,如果你扩展这样的对象,您还需要确保对象所属的“class”(在这种情况下为```fabric.Rect```)在“stateProperties”数组中具有此属性,以便从字符串表示中加载canvas将解析并将其正确添加到对象中。 331 | 332 | 您可以将对象标记为不可导出设置```excludeFromExport```为```true```。以这种方式,一些在canvas上用来辅助的对象在序列化过程中不会被保存。 333 | 334 | ### toSVG 335 | 336 | 另一种高效的基于文本的画布表示是SVG格式。由于Fabric专门从事画布上的SVG解析和渲染,所以将其作为双向过程并提供画布到SVG转换是有意义的。让我们将相同的矩形添加到画布中,并查看从toSVG方法返回的表示形式: 337 | 338 | ```js 339 | canvas.add(new fabric.Rect({ 340 | left: 50, 341 | top: 50, 342 | height: 20, 343 | width: 20, 344 | fill: 'green' 345 | })); 346 | console.log(canvas.toSVG()); 347 | ``` 348 | 349 | 输出: 350 | 351 | ``` 352 | 'Created with Fabric.js 0.9.21' 353 | ``` 354 | 355 | 就像用```toJSON```和```toObject```一样,```toSVG```在canvas上调用时,将其逻辑委托给每个单独的对象,并且每个单独的对象都有自己的toSVG方法,它是特殊的对象类型。如果您需要修改或扩展对象的SVG表示,那么可以使用```toSVG```做同样的事情,就像我们对```toObject```一样。 356 | 357 | 与Fabric的专有的```toObject / toJSON```相比,SVG表示的好处是可以将其投放到任何支持SVG的渲染器(浏览器,应用程序,打印机,相机等)中,并且它应该可以正常工作。但是,您首先需要将其加载到canvas画布上。谈到在画布上加载东西,现在我们可以将画布序列化成一个有效的文本块,我们将如何加载到画布上? 358 | 359 | ## 反序列化,SVG解析器 360 | 361 | 与序列化类似,有两种方式从字符串加载canvas:从JSON表示,或从SVG。当使用JSON表示时,使用```loadFromJSON```和```oadFromDatalessJSON```方法。SVG时,使用```fabric.loadSVGFromURL```和```fabric.loadSVGFromString```。 362 | 363 | 请注意,前2个方法是实例方法,直接在canvas实例上调用,而最后2个方法是静态方法,在“fabric”对象上而不是在canvas上调用。 364 | 365 | 这些方法没有什么可说的。他们的工作正如你所期望的那样。例如,我们先从画布中获取以前的JSON输出并将其加载到干净的画布上: 366 | 367 | ```js 368 | var canvas = new fabric.Canvas(); 369 | 370 | 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)"}',() => { }); 371 | ``` 372 | 373 | 两个物体“神奇”出现在画布上: 374 | 375 | ![ ](http://fabricjs.com/article_assets/3_5.png) 376 | 377 | 所以从字符串加载画布是很容易的。但是那个奇怪的```loadFromDatalessJSON```方法是干什么的?与我们刚刚使用的```loadFromJSON```有什么不同?为了理解为什么我们需要这种方法,我们需要看一下具有或多或少的复杂路径对象的序列化画布。像这个: 378 | 379 | ![ ](http://fabricjs.com/article_assets/3_6.png) 380 | 381 | 而这个形状的```JSON.stringify(canvas)```输出是: 382 | 383 | ``` 384 | {"objects":[{"type":"path","left":184,"top":177,"width":175,"height":151,"fill":"#231F20","overlayFill":null,"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":-19,"flipX":false,"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":false,"transparentCorners":true,"perPixelTargetFind":false,"path":[["M",39.502,61.823],["c",-1.235,-0.902,-3.038,-3.605,-3.038,-3.605],["s",0.702,0.4,3.907,1.203],["c",3.205,0.8,7.444,-0.668,10.114,-1.97],["c",2.671,-1.302,7.11,-1.436,9.448,-1.336],["c",2.336,0.101,4.707,0.602,4.373,2.036],["c",-0.334,1.437,-5.742,3.94,-5.742,3.94],["s",0.4,0.334,1.236,0.334],["c",0.833,0,6.075,-1.403,6.542,-4.173],["s",-1.802,-8.377,-3.272,-9.013],["c",-1.468,-0.633,-4.172,0,-4.172,0],["c",4.039,1.438,4.941,6.176,4.941,6.176],["c",-2.604,-1.504,-9.279,-1.234,-12.619,0.501],["c",-3.337,1.736,-8.379,2.67,-10.083,2.503],["c",-1.701,-0.167,-3.571,-1.036,-3.571,-1.036],["c",1.837,0.034,3.239,-2.669,3.239,-2.669],["s",-2.068,2.269,-5.542,0.434],["c",-3.47,-1.837,-1.704,-8.18,-1.704,-8.18],["s",-2.937,5.909,-1,9.816],["C",34.496,60.688,39.502,61.823,39.502,61.823],["z"],["M",77.002,40.772],["c",0,0,-1.78,-5.03,-2.804,-8.546],["l",-1.557,8.411],["l",1.646,1.602],["c",0,0,0,-0.622,-0.668,-1.691],["C",72.952,39.48,76.513,40.371,77.002,40.772],["z"],["M",102.989,86.943],["M",102.396,86.424],["c",0.25,0.22,0.447,0.391,0.594,0.519],["C",102.796,86.774,102.571,86.578,102.396,86.424],["z"],["M",169.407,119.374],["c",-0.09,-5.429,-3.917,-3.914,-3.917,-2.402],["c",0,0,-11.396,1.603,-13.086,-6.677],["c",0,0,3.56,-5.43,1.69,-12.461],["c",-0.575,-2.163,-1.691,-5.337,-3.637,-8.605],["c",11.104,2.121,21.701,-5.08,19.038,-15.519],["c",-3.34,-13.087,-19.63,-9.481,-24.437,-9.349],["c",-4.809,0.135,-13.486,-2.002,-8.011,-11.618],["c",5.473,-9.613,18.024,-5.874,18.024,-5.874],["c",-2.136,0.668,-4.674,4.807,-4.674,4.807],["c",9.748,-6.811,22.301,4.541,22.301,4.541],["c",-3.097,-13.678,-23.153,-14.636,-30.041,-12.635],["c",-4.286,-0.377,-5.241,-3.391,-3.073,-6.637],["c",2.314,-3.473,10.503,-13.976,10.503,-13.976],["s",-2.048,2.046,-6.231,4.005],["c",-4.184,1.96,-6.321,-2.227,-4.362,-6.854],["c",1.96,-4.627,8.191,-16.559,8.191,-16.559],["c",-1.96,3.207,-24.571,31.247,-21.723,26.707],["c",2.85,-4.541,5.253,-11.93,5.253,-11.93],["c",-2.849,6.943,-22.434,25.283,-30.713,34.274],["s",-5.786,19.583,-4.005,21.987],["c",0.43,0.58,0.601,0.972,0.62,1.232],["c",-4.868,-3.052,-3.884,-13.936,-0.264,-19.66],["c",3.829,-6.053,18.427,-20.207,18.427,-20.207],["v",-1.336],["c",0,0,0.444,-1.513,-0.089,-0.444],["c",-0.535,1.068,-3.65,1.245,-3.384,-0.889],["c",0.268,-2.137,-0.356,-8.549,-0.356,-8.549],["s",-1.157,5.789,-2.758,5.61],["c",-1.603,-0.179,-2.493,-2.672,-2.405,-5.432],["c",0.089,-2.758,-1.157,-9.702,-1.157,-9.702],["c",-0.8,11.75,-8.277,8.011,-8.277,3.74],["c",0,-4.274,-4.541,-12.82,-4.541,-12.82],["s",2.403,14.421,-1.336,14.421],["c",-3.737,0,-6.944,-5.074,-9.879,-9.882],["C",78.161,5.874,68.279,0,68.279,0],["c",13.428,16.088,17.656,32.111,18.397,44.512],["c",-1.793,0.422,-2.908,2.224,-2.908,2.224],["c",0.356,-2.847,-0.624,-7.745,-1.245,-9.882],["c",-0.624,-2.137,-1.159,-9.168,-1.159,-9.168],["c",0,2.67,-0.979,5.253,-2.048,9.079],["c",-1.068,3.828,-0.801,6.054,-0.801,6.054],["c",-1.068,-2.227,-4.271,-2.137,-4.271,-2.137],["c",1.336,1.783,0.177,2.493,0.177,2.493],["s",0,0,-1.424,-1.601],["c",-1.424,-1.603,-3.473,-0.981,-3.384,0.265],["c",0.089,1.247,0,1.959,-2.849,1.959],["c",-2.846,0,-5.874,-3.47,-9.078,-3.116],["c",-3.206,0.356,-5.521,2.137,-5.698,6.678],["c",-0.179,4.541,1.869,5.251,1.869,5.251],["c",-0.801,-0.443,-0.891,-1.067,-0.891,-3.473],... 385 | ``` 386 | 387 | ...这只是整个输出的其中一部分! 388 | 389 | 这里发生了什么?事实证明,这个```fabric.Path```实例(这个形状)包括数百条贝塞尔线,决定了它是如何被渲染的。JSON表示中的所有这些```[“c”,0,2.67,-0.979,5.253,-2.048,9.079]```数据片段对应于这些曲线中的每一个。而当数百(甚至数千)的这些数据片段构成画布的表现最终是相当巨大的。 390 | 391 | 该怎么办? 392 | 这时使用```toDatalessJSON```会方便很多。我们来试试吧: 393 | 394 | ```js 395 | canvas.item(0).sourcePath = '/assets/dragon.svg'; 396 | console.log(JSON.stringify(canvas.toDatalessJSON())); 397 | ``` 398 | 399 | 输出: 400 | 401 | ``` 402 | {"objects":[{"type":"path","left":143,"top":143,"width":175,"height":151,"fill":"#231F20","overlayFill":null,"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1,"scaleY":1,"angle":-19,"flipX":false,"flipY":false,"opacity":1,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":false,"transparentCorners":true,"perPixelTargetFind":false,"path":"/assets/dragon.svg"}],"background":"rgba(0, 0, 0, 0)"} 403 | ``` 404 | 405 | 这样看起来小多了,发生了什么?注意在调用```toDatalessJSON```之前做了什么,我们给了“sourcePath”属性一个路径:“/assets/dragon.svg”,然后,当我们调用```toDatalessJSON```时,从前一个输出(这几百个路径命令)的整个堆积如山的路径字符串被一个单独的“dragon.svg”字符串所取代。你可以看到它显示在上面。 406 | 407 | 当使用大量复杂的形状时,```toDessessJSON```可以让我们进一步减少画布表现,并用简单的SVG链接代替巨大的路径数据表示。 408 | 409 | 现在回到```loadFromDatalessJSON```方法...你可以猜到,它只是允许从无数据版本的canvas表示中加载canvas。```loadFromDatalessJSON``几乎非常了解如何使用这些“路径”字符串(如“/assets/dragon.svg”),加载它们,并用作相应路径对象的数据。 410 | 411 | 现在,我们来看看SVG加载方法。我们可以使用字符串或URL: 412 | 413 | ```js 414 | fabric.loadSVGFromString('...', function(objects, options) { 415 | var obj = fabric.util.groupSVGElements(objects, options); 416 | canvas.add(obj).renderAll(); 417 | }); 418 | ``` 419 | 420 | 第一个参数是SVG字符串,第二个是回调函数,当SVG被解析和加载时调用回调,并且接收到2个参数 - ```objects```和```option```,```objects```包含从SVG路径解析的对象数组,路径组(复杂对象),图像,文本等。为了将所有这些对象分组成一个连贯的集合,并使它们与SVG文档中的方式相同,我们使用```fabric.util.groupSVGElements```传递```objects```和```option```。在返回值中,我们可以获得```fabric.Path```或```fabric.Group```的一个实例,然后我们可以将其添加到画布上。 421 | 422 | ```fabric.loadSVGFromURL```的工作方式相同,除了将SVG内容的字符串替换为URL,还要注意,Fabric将尝试通过XMLHttpRequest获取该URL,因此SVG需要符合通常的SOP(标准操作程序)规则。 423 | 424 | ## 子类 425 | 426 | 由于Fabric以真正的面向对象的方式构建,它旨在使子类化和扩展简单自然。从这个系列的第1部分你知道,Fabric中存在对象的现有层次结构。所有2D对象(路径,图像,文本等),还有一些“classes”,都继承自```fabric.Object```,像```fabric.IText```,甚至形成3级继承。 427 | 428 | 那么我们怎么去把Fabric中的一个现有的“class”分类呢?或者甚至创造自己的? 429 | 430 | 对于这个任务,我们需要使用```fabric.util.createClass```实用程序。```createClass```只不过是对JavaScript的原型继承的简单抽象。我们先创建一个简单的Point“class”: 431 | 432 | ```js 433 | var Point = fabric.util.createClass({ 434 | initialize: function(x, y) { 435 | this.x = x || 0; 436 | this.y = y || 0; 437 | }, 438 | toString: function() { 439 | return this.x + '/' + this.y; 440 | } 441 | }); 442 | ``` 443 | 444 | ```createClass```接受一个对象并使用该对象的属性创建具有实例级属性的“class”。唯一需要特别处理的属性是“initialize”用作构造函数(相当于constructor)。所以现在,当初始化Point时,我们将创建一个带有“x”和“y”属性的实例,和“toString”方法: 445 | 446 | ```js 447 | var point = new Point(10, 20); 448 | 449 | point.x; // 10 450 | point.y; // 20 451 | 452 | point.toString(); // "10/20" 453 | 454 | ``` 455 | 456 | 如果我们想创建一个“Point”类的子类“ColoredPoint”,我们将使用createClass: 457 | 458 | ```js 459 | var ColoredPoint = fabric.util.createClass(Point, { 460 | initialize: function(x, y, color) { 461 | this.callSuper('initialize', x, y); 462 | this.color = color || '#000'; 463 | }, 464 | toString: function() { 465 | return this.callSuper('toString') + ' (color: ' + this.color + ')'; 466 | } 467 | }); 468 | ``` 469 | 470 | 注意如何将具有实例级属性的对象作为第二个参数传递,而第一个参数接收```Point```“class”,这告诉```createClass```将其用作这个父类的“class”。为了避免重复,我们使用的是```callSuper```方法,它调用父类“class”的方法。这意味着如果我们要改变```Point```,这些更改也会传播到```ColoredPoint```。看```ColoredPoint```的行为: 471 | 472 | ```js 473 | var redPoint = new ColoredPoint(15, 33, '#f55'); 474 | 475 | redPoint.x; // 15 476 | redPoint.y; // 33 477 | redPoint.color; // "#f55" 478 | 479 | redPoint.toString(); "15/35 (color: #f55)" 480 | ``` 481 | 482 | 所以现在我们去创建我们自己的“类”和“子类”,我们来看看如何使用已经存在的Fabric。例如,我们创建一个LabeledRect“类”,它基本上是一个与它相关联的一些标签的矩形。当在画布上渲染时,该标签将被表示为矩形内的文本。类似于以前的组合示例与圆和文本。当您使用Fabric时,您会注意到,可以通过使用组合或使用自定义类来实现这样的组合抽象。 483 | 484 | ```js 485 | var LabeledRect = fabric.util.createClass(fabric.Rect, { 486 | 487 | type: 'labeledRect', 488 | 489 | initialize: function(options) { 490 | options || (options = { }); 491 | 492 | this.callSuper('initialize', options); 493 | this.set('label', options.label || ''); 494 | }, 495 | 496 | toObject: function() { 497 | return fabric.util.object.extend(this.callSuper('toObject'), { 498 | label: this.get('label') 499 | }); 500 | }, 501 | 502 | _render: function(ctx) { 503 | this.callSuper('_render', ctx); 504 | 505 | ctx.font = '20px Helvetica'; 506 | ctx.fillStyle = '#333'; 507 | ctx.fillText(this.label, -this.width/2, -this.height/2 + 20); 508 | } 509 | }); 510 | ``` 511 | 512 | 在这里似乎还有很多事情,但实际上很简单。 513 | 514 | 首先,我们将父类“class”指定为```fabric.Rect```,以利用其渲染能力。接下来,我们定义“type”属性,将其设置为“labeledRect”。这只是为了一致,因为所有的Fabric对象都有类型属性(rect,circle,path,text等)。然后,我们已经熟悉的构造函数(```initialize```),我们再次使用```callSuper```。此外,我们将对象的标签设置为通过选项传递的值。最后,我们剩下两个方法:```toObject```和```_render```。 ```toObject```,正如您从序列化章节已经知道的那样,负责实例的对象(和JSON)表示。由于LabeledRect具有与常规```Rect```相同的属性,也是一个标签,因此我们将扩展父类的```toObject```方法,并在其中添加标签。最后但并非最不重要的是,```_rendermethod```是实际绘制实例的原因。还有一个```callSuper```调用,它是渲染矩形,有3行文本渲染逻辑。 515 | 516 | 现在,如果我们要渲染这样的对象: 517 | 518 | ```js 519 | var labeledRect = new LabeledRect({ 520 | width: 100, 521 | height: 50, 522 | left: 100, 523 | top: 100, 524 | label: 'test', 525 | fill: '#faa' 526 | }); 527 | canvas.add(labeledRect); 528 | ``` 529 | 530 | 我们会得到这个: 531 | 532 | ![ ](http://fabricjs.com/article_assets/3_7.png) 533 | 534 | 更改标签值或任何其他的矩形属性将按预期工作: 535 | 536 | ```js 537 | labeledRect.set({ 538 | label: 'trololo', 539 | fill: '#aaf', 540 | rx: 10, 541 | ry: 10 542 | }); 543 | ``` 544 | 545 | ![ ](http://fabricjs.com/article_assets/3_8.png) 546 | 547 | 当然,在这一点上,你可以随意修改这个“类”的行为。例如,将某些值设置为默认值,以避免每次传递给构造函数。或者使实例上的某些可配置属性可用。如果您使其他属性可配置,则可能需要在```toObject```和```initialize```中对其进行计算。 548 | 549 | ```js 550 | ... 551 | initialize: function(options) { 552 | options || (options = { }); 553 | 554 | this.callSuper('initialize', options); 555 | 556 | // 给所有标注的矩形固定宽/高100/50 557 | this.set({ width: 100, height: 50 }); 558 | 559 | this.set('label', options.label || ''); 560 | } 561 | ... 562 | _render: function(ctx) { 563 | 564 | // 使标签的字体和填充值可配置 565 | 566 | ctx.font = this.labelFont; 567 | ctx.fillStyle = this.labelFill; 568 | 569 | ctx.fillText(this.label, -this.width/2, -this.height/2 + 20); 570 | } 571 | ... 572 | ``` 573 | 574 | 在这个说明中,我正在包装这个系列的第三部分,其中我们分为了Fabric的一些更先进的方面。在群组,课程和(反)序列化的帮助下,您可以将您的应用程序提升到一个全新的水平。 -------------------------------------------------------------------------------- /part-4.md: -------------------------------------------------------------------------------- 1 | # Fabric.js介绍 第四部分 2 | 3 | 我们已经在前几个系列涵盖了很多话题,从基础对象操作到动画,事件,滤镜,组合和子类。但还有几件非常有趣和有用的事情要讨论! 4 | 5 | ## 自由绘画 6 | 7 | 如果说有什么功能能让``````在眼前一亮,那一定是它能够很好的支持自由绘图!由于画布只是一个2D位图, Fabric为我们提供了一张纸:可以自由绘画,而且非常自然。 8 | 9 | 只需将Fabric canvas的```isDrawingMode```属性设置为```true```即可实现自由绘制模式。这样画布上的点击和移动就会被立刻解释为铅笔或刷子。 10 | 11 | 当```isDrawingMode```为```true```时,您可以随意在画布上多次绘制,但是一旦你在画布上做任何动作,然后触发“mouseup”事件,Fabric就会触发"path:created" 事件,并且将刚刚的绘画转变为```fabric.Path```的实例, 12 | 13 | 如果在任何时刻,将```isDrawingMode```设置为```false```,你创建的路径对象会仍然存在于画布上。而且由于它们是```fabric.Path```对象,所以可以以任何方式修改它们: 移动,旋转,缩放等。 14 | 15 | 还有两个属性可以定制自由绘图: ```freeDrawingBrush.color``` and ```freeDrawingBrush.width```,两者都可以通过```freeDrawingBrush```实例在Fabric画布实例上使用。```freeDrawingBrush.color```可以是任何常规的颜色值,代表画笔的颜色。```freeDrawingBrush.width```是一个像素,代表画笔的宽度。 16 | 17 | ![ ](http://fabricjs.com/article_assets/4_1.png) 18 | 19 | 在不久的将来,我们计划添加更多的自由绘图选项:各种版本的画笔(如喷雾式或粉笔式)。还可以自定义画笔模式,还有一个可以自己扩展的选项,类似于Fabric图像滤镜。 20 | 21 | ## 自定义 22 | 23 | 一件有趣的事情是如何去自定义Fabric,您可以在```canvas```或```canvas```对象上调整数十种各样的参数,以使事情按照所需的方式运行。我们来看看其中的一些。 24 | 25 | ### 锁定对象 26 | 27 | 画布上的每个对象都可以以几种方式锁定。“lockMovementX”,“lockMovementY”,“lockRotation”,“lockScalingX”,“lockScalingY”是锁定相应对象动作的属性。所以将某个对象的```lockMovementX```属性设置为```true```会阻止对象被水平移动,此时你仍然可以垂直移动它。类似的,```lockRotation```可以阻止旋转,```lockScalingX``` / ```lockScalingY``` 可以阻止水平或者垂直缩放。所有这些都是可以叠加的,你可以将它们组合在一起使用。 28 | 29 | ### 改变边框和角 30 | 31 | 您可以通过“hasControls”和“hasBorders”属性来控制对象的边框和角的可见性。只是将它们设置为```false```。 32 | 33 | ```js 34 | object.hasBorders = false; 35 | ``` 36 | 37 | ![ ](http://fabricjs.com/article_assets/4_2.png) 38 | 39 | ```js 40 | object.hasControls = false; 41 | ``` 42 | 43 | ![ ](http://fabricjs.com/article_assets/4_3.png) 44 | 45 | 您还可以通过某些自定义属性来调整它的外观:“cornerDashArray”,“borderDashArray”,“borderColor”,“transparentCorners”“cornerColor”,“cornerStrokeColor”,“cornerStyle”,“selectionBackgroundColor”,“padding”和“cornerSize”。 46 | 47 | ```js 48 | object.set({ 49 | borderColor: 'red', 50 | cornerColor: 'green', 51 | cornerSize: 6 52 | }); 53 | ``` 54 | 55 | ![ ](http://fabricjs.com/article_assets/4_4.png) 56 | 57 | ```js 58 | object.set({ 59 | transparentCorners: false, 60 | cornerColor: 'blue', 61 | cornerStrokeColor: 'red', 62 | borderColor: 'red', 63 | cornerSize: 12, 64 | padding: 10, 65 | cornerStyle: 'circle', 66 | borderDashArray: [3, 3] 67 | }); 68 | 69 | ``` 70 | 71 | ![ ](http://fabricjs.com/article_assets/4_4b.png) 72 | 73 | ### 禁用选择 74 | 75 | 您可以通过将canvas的“selection”属性设置为```false```来禁用画布上的对象选择,这可以防止在画布上选中任何对象。如果只需要使某些对象不可选,则可以更改某个对象的“selectable”属性,只是将其设置为```false```,并且对象失去其交互性。 76 | 77 | ### 自定义选择 78 | 79 | 现在,如果你不想禁用选择,而是要改变它的外观呢?没问题。 80 | 81 | canvas画布上有4个属性来控制它的表现: "selectionColor", "selectionBorderColor", "selectionLikeWidth", and "selectionDashArray"。这些都非常的语义化,让我们来看一个例子: 82 | 83 | ```js 84 | canvas.add(new fabric.Circle({ radius: 30, fill: '#f55', top: 100, left: 100 })); 85 | 86 | canvas.selectionColor = 'rgba(0,255,0,0.3)'; 87 | canvas.selectionBorderColor = 'red'; 88 | canvas.selectionLineWidth = 5; 89 | ``` 90 | 91 | ![ ](http://fabricjs.com/article_assets/4_8.png) 92 | 93 | 最后一个属性“selectionDashArray”可不是那么简单,他允许我们使用虚线作为选择线。定义虚线模式的方式是通过数组指定间隔。因此,要创建一个模式,其中有一个大点的数字,后面跟着一个小一点的数字来代表虚线的间隔,我们可以使用```[10,5]```,作为“selectionDashArray”。这将画出10px长的线,然后跳过5px,再画一个10px长度的线,以此类推。如果我们使用```[2, 4, 6]```的数组,这样将通过绘制2px长的线,然后跳过4px,然后绘制6px线,然后跳过2px,然后绘制4px线,然后跳过6px,以此类推。例如,这就是```[5,10]```规则的样子: 94 | 95 | ![ ](http://fabricjs.com/article_assets/4_9.png) 96 | 97 | ### 虚线描边 98 | 99 | 与canvas上的“selectionDashArray”类似,所有Fabric对象都有“strokeDashArray”属性,负责在对象上执行虚线的描边操作。 100 | 101 | ```js 102 | var rect = new fabric.Rect({ 103 | fill: '#06538e', 104 | width: 125, 105 | height: 125, 106 | stroke: 'red', 107 | strokeDashArray: [5, 5] 108 | }); 109 | canvas.add(rect); 110 | ``` 111 | 112 | ![ ](http://fabricjs.com/article_assets/4_13.png) 113 | 114 | ### 可点击区域 115 | 116 | 如你所知,所有的Fabric对象都有边界边框,用于拖动、旋转、缩放。您可能已经注意到,当用于控制的角和边框存在时,即使在单击没有绘制内容的对象边框内的空白时也可以拖动该对象。 117 | 118 | 看看这个图像: 119 | 120 | ![ ](http://fabricjs.com/article_assets/4_14.png) 121 | 122 | 默认情况下,画布上的所有Fabric对象都可以通过边框拖动。如果你想要不同的行为,例如根据Fabric对象的实际内容进行点击或拖拽,您可以在对象上使用“perPixelTargetFind”属性。只需将其设置为```true```即可获得所需的行为。 123 | 124 | ### 旋转控件 125 | 126 | 由于版本1.0 Fabric在默认情况下使用默认UI,对象不能同时缩放和旋转。作为代替的是每个对象都有一个单独的旋转控件。该控件的相应属性为“hasRotatingPoint”,默认是```true```,将其设置为```false```可以禁用旋转控制。您也可以通过“rotatePointOffset”数字属性自定义其相对于对象的偏移量。 127 | 128 | ![ ](http://fabricjs.com/article_assets/4_10.png) 129 | 130 | ### Fabric对象变形 131 | 132 | 自**1.0**版以来,Fabric中还提供了许多其他与转换相关的属性,其中一个是画布实例上的“uniScaleTransform”。默认为```false```,可用于启用对象的非均匀缩放;换句话说,它允许在通过角拖动时改变对象的比例。 133 | 134 | ![ ](http://fabricjs.com/article_assets/4_15.png) 135 | 136 | 有“centeredScaling”和“centeredRotation”属性(在v1.3.4之前是一个属性“centerTransform”)。他们指定对象的中心是否应该用作转换的起点。当它们都设置为```true```,对象总是从中心缩放/旋转时,它会和**1.0**之前的行为相同。由于**1.0**变形原点是动态的,这样可以在缩放对象时进行更精细的控制。 137 | 138 | 最后一对新属性是“originX”和“originY”。默认设置为“left”和“top”,它们允许以编程的方式更改变形的原点。当你拖动对象的角时,就是这些属性正在动态变化。 139 | 140 | 那么什么时候需要我们手动更改它们?举个例子,当我们处理文本对象时,当您动态的改变文本并且文本框尺寸变大,“originX”和“originY”指出了增长的位置。所以如果你需要文本对象居中时,你可以将originX设置为“center”。如果让文本移到右边,你需要将“originX”设置为“right”等等。这种行为类似于CSS中的“position:absolute”。 141 | 142 | ### 画布的背景和叠加 143 | 144 | 从第一部分可以记得,您可以指定一个颜色来填充整个画布背景。只需将任意常规颜色值设置为canvas的“backgroundColor”属性。 145 | 146 | ```js 147 | canvas.add(new fabric.Circle({ radius: 30, fill: '#f55', top: 100, left: 100 })); 148 | canvas.backgroundColor = 'rgba(0,0,255,0.3)'; 149 | canvas.renderAll(); 150 | ``` 151 | 152 | ![ ](http://fabricjs.com/article_assets/4_5.png) 153 | 154 | 你可以进一步去分配图像作为背景。您需要使用```setBackgroundImage```方法,传递url和一个可选的完成回调。 155 | 156 | ```js 157 | canvas.add(new fabric.Circle({ radius: 30, fill: '#f55', top: 100, left: 100 })); 158 | canvas.setBackgroundImage('../assets/pug.jpg', canvas.renderAll.bind(canvas)); 159 | ``` 160 | 161 | ![ ](http://fabricjs.com/article_assets/4_6.png) 162 | 163 | 最后,您还可以设置叠加图像,在这种情况下,它将始终显示在画布上呈现的任何对象之上。只需使用setOverlayImage,传递url和一个可选的完成回调。 164 | 165 | ```js 166 | canvas.add(new fabric.Circle({ radius: 30, fill: '#f55', top: 100, left: 100 })); 167 | canvas.setOverlayImage('../assets/jail_cell_bars.png', canvas.renderAll.bind(canvas)); 168 | ``` 169 | 170 | ![ ](http://fabricjs.com/article_assets/4_7.png) 171 | 172 | ## Node.js上的Fabric 173 | 174 | Fabric的独特之处在于它不仅可以在客户端,浏览器中也可以在服务器上工作!当您要从客户端发送数据并在服务器上直接创建该数据的映像时,这可能很有用。或者如果您为了速度,方便性或其他原因,只是想从控制台使用Fabric API。 175 | 176 | 让我们来看看如何设置在Node环境下运行Fabric。 177 | 178 | 首先,您需要安装[Node.js](http://nodejs.org/),如果还没有。有几种方法来安装Node,具体取决于你的操作系统。您可以按照[这些说明](http://howtonode.org/how-to-install-nodejs)或[这些说明](https://github.com/joyent/node/wiki/Installation)。 179 | 180 | Node安装成功之后,我们需要安装[node-canvas](https://github.com/LearnBoost/node-canvas)库,node-canvas是NodeJS的Canvas实现。它依赖[Cairo](http://cairographics.org/)- 可以在Mac,Linux或Windows上运行的2D图形库。node-canvas具有专门的[安装说明](https://github.com/LearnBoost/node-canvas/wiki),具体取决于您的操作系统。 181 | 182 | 由于Fabric运行在Node上,它作为一个NPM包。所以下一步是安装NPM。您可以在其[github repo](https://github.com/isaacs/npm)中找到安装说明,不过一般情况下当您安装Node的时候会自动安装了NPM。 183 | 184 | 最后一步是安装[Fabric](https://npmjs.org/package/fabric)包,使用NPM。这通过运行```npm install fabric```(或```npm install -g fabric```来全局安装包)完成。 185 | 186 | 我们现在运行Node控制台,应该就使用node-canvas和Fabric了: 187 | 188 | ```bash 189 | > node 190 | ... 191 | > typeof require('canvas'); // "function" 192 | > typeof require('fabric'); // "object" 193 | ``` 194 | 195 | 现在一切都准备好了,我们可以尝试一个简单的“helloworld”的测试。让我们创建一个helloworld.js文件: 196 | 197 | ```js 198 | var fs = require('fs'), 199 | fabric = require('fabric').fabric, 200 | out = fs.createWriteStream(__dirname + '/helloworld.png'); 201 | 202 | var canvas = fabric.createCanvasForNode(200, 200); 203 | var text = new fabric.Text('Hello world', { 204 | left: 100, 205 | top: 100, 206 | fill: '#f55', 207 | angle: 15 208 | }); 209 | canvas.add(text); 210 | 211 | var stream = canvas.createPNGStream(); 212 | stream.on('data', function(chunk) { 213 | out.write(chunk); 214 | }); 215 | ``` 216 | 217 | 然后运行它作为```node helloworld.js```,打开helloworld.png: 218 | 219 | ![ ](http://fabricjs.com/article_assets/4_11.png) 220 | 221 | 那么这里发生了什么?我们来看看这段代码的重要部分。 222 | 223 | 首先,我们引入了Fabric```fabric = require('fabric').fabric```,然后我们用```fabric.createCanvasForNode()```替代了平时使用的```new fabric.Canvas()```来创建了Fabric的canvas画布,此方法将width和height作为参数,并创建该大小的画布(在这种不传参的情况下为200x200)。 224 | 225 | 然后有熟悉的对象创建(```new fabric.Text()```)和添加到画布(```canvas.add(text)```)。 226 | 227 | 到目前做的一切都只是创建了一个Fabric画布,然后渲染了一个text文本,现在,如何创建在画布上呈现的任何图像?在canvas实例上使用可用的```createPNGStream```方法。```createPNGStream```返回Node的[流](http://nodejs.org/api/stream.html),然后可以使用```on('data')```输出到图像文件中,并将其写入与图像文件(```fs.createWriteStream()```)对应的流中。 228 | 229 | ```fabric.createCanvasForNode```和```canvas.createPNGStream```几乎是Node特有的唯一2种方法,其他一切都是一样的。您仍然可以按照通常的方式创建对象,将它们添加到画布上,修改,渲染等。值得一提的是,当您通过```createCanvasForNode```创建画布时,它将使用```nodeCanvas```属性进行扩展,该属性是对原始节点画布实例的引用。 230 | 231 | ### Node服务器与Fabric 232 | 233 | 例如,让我们创建一个简单的Node服务器,它将使用JSON格式的Fabric数据收听传入的请求,并输出该数据的图像。整个脚本只有25行长! 234 | 235 | ```js 236 | var fabric = require('fabric').fabric, 237 | http = require('http'), 238 | url = require('url'), 239 | PORT = 8124; 240 | 241 | var server = http.createServer(function (request, response) { 242 | var params = url.parse(request.url, true); 243 | var canvas = fabric.createCanvasForNode(200, 200); 244 | 245 | response.writeHead(200, { 'Content-Type': 'image/png' }); 246 | 247 | canvas.loadFromJSON(params.query.data, function() { 248 | canvas.renderAll(); 249 | 250 | var stream = canvas.createPNGStream(); 251 | stream.on('data', function(chunk) { 252 | response.write(chunk); 253 | }); 254 | stream.on('end', function() { 255 | response.end(); 256 | }); 257 | }); 258 | }); 259 | 260 | server.listen(PORT); 261 | ``` 262 | 263 | 此片段中的大部分代码应该已经很熟悉了。它的要点在于服务器响应。我们正在创建Fabric画布,将JSON数据加载到其中,渲染它,并将最终结果作为服务器响应进行流式传输。 264 | 265 | 要测试它,我们来获取一个绿色,并且稍微旋转的矩形: 266 | 267 | ``` 268 | {"objects":[{"type":"rect","left":103.85,"top":98.85,"width":50,"height":50,"fill":"#9ae759","overlayFill":null,"stroke":null,"strokeWidth":1,"strokeDashArray":null,"scaleX":1.39,"scaleY":1.39,"angle":30,"flipX":false,"flipY":false,"opacity":0.8,"selectable":true,"hasControls":true,"hasBorders":true,"hasRotatingPoint":false,"transparentCorners":true,"perPixelTargetFind":false,"rx":0,"ry":0}],"background":"rgba(0, 0, 0, 0)"} 269 | ``` 270 | 271 | URL编码后: 272 | 273 | ``` 274 | %7B"objects"%3A%5B%7B"type"%3A"rect"%2C"left"%3A103.85%2C"top"%3A98.85%2C"width"%3A50%2C"height"%3A50%2C"fill"%3A"%239ae759"%2C"overlayFill"%3Anull%2C"stroke"%3Anull%2C"strokeWidth"%3A1%2C"strokeDashArray"%3Anull%2C"scaleX"%3A1.39%2C"scaleY"%3A1.39%2C"angle"%3A30%2C"flipX"%3Afalse%2C"flipY"%3Afalse%2C"opacity"%3A0.8%2C"selectable"%3Atrue%2C"hasControls"%3Atrue%2C"hasBorders"%3Atrue%2C"hasRotatingPoint"%3Afalse%2C"transparentCorners"%3Atrue%2C"perPixelTargetFind"%3Afalse%2C"rx"%3A0%2C"ry"%3A0%7D%5D%2C"background"%3A"rgba(0%2C%200%2C%200%2C%200)"%7D 275 | ``` 276 | 277 | 并通过“data”查询参数传递给服务器。立即回应“image / png”的Content-type,如下所示: 278 | 279 | 正如你所看到的,在服务器上使用Fabric是非常简单直接的。随意试验这个片段。可以从URL参数中更改画布尺寸,或者在返回图像作为响应之前修改客户端数据。 280 | 281 | ### 在Node环境自定义Fabric的字体 282 | 283 | 在我们可以在Fabric中使用自定义字体之前,我们需要先加载它们。在浏览器(客户端)中,加载字体的最常用方法是使用[CSS3 @font-face](http://www.w3.org/TR/css3-fonts/#font-face-rule)规则。 在Node(服务器端)的Fabric中,我们可以使用node-canvas的**Font API**加载字体。 284 | 285 | 下面的示例演示如何加载和使用自定义字体。将其保存到customfont.js并确保字体文件的路径正确。在这个例子中,我们使用[Ubuntu](http://www.google.com/fonts/specimen/Ubuntu)作为自定义字体。 286 | 287 | ```js 288 | var fs = require('fs'), 289 | fabric = require('fabric').fabric, 290 | canvas = fabric.createCanvasForNode(300, 250); 291 | 292 | var font = new canvas.Font('Ubuntu', __dirname + '/fonts/Ubuntu-Regular.ttf'); 293 | font.addFace(__dirname + '/fonts/Ubuntu-Bold.ttf', 'bold'); 294 | font.addFace(__dirname + '/fonts/Ubuntu-Italic.ttf', 'normal', 'italic'); 295 | font.addFace(__dirname + '/fonts/Ubuntu-BoldItalic.ttf', 'bold', 'italic'); 296 | 297 | canvas.contextContainer.addFont(font); // 使用 createPNGStream 或者 createJPEGStream 的时候 298 | canvas.contextTop.addFont(font); // 使用 toDataURL 或者 toDataURLWithMultiplier的时候 299 | 300 | var text = new fabric.Text('regular', { 301 | left: 150, 302 | top: 50, 303 | fontFamily: 'Ubuntu' 304 | }); 305 | canvas.add(text); 306 | 307 | text = new fabric.Text('bold', { 308 | left: 150, 309 | top: 100, 310 | fontFamily: 'Ubuntu', 311 | fontWeight: 'bold' 312 | }); 313 | canvas.add(text); 314 | 315 | text = new fabric.Text('italic', { 316 | left: 150, 317 | top: 150, 318 | fontFamily: 'Ubuntu', 319 | fontStyle: 'italic' 320 | }); 321 | canvas.add(text); 322 | 323 | text = new fabric.Text('bold italic', { 324 | left: 150, 325 | top: 200, 326 | fontFamily: 'Ubuntu', 327 | fontWeight: 'bold', 328 | fontStyle: 'italic' 329 | }); 330 | canvas.add(text); 331 | 332 | var out = fs.createWriteStream(__dirname + '/customfont.png'); 333 | var stream = canvas.createPNGStream(); 334 | stream.on('data', function(chunk) { 335 | out.write(chunk); 336 | }); 337 | ``` 338 | 339 | 运行```node customfont.js```创建一个图像(customfont.png),如下所示: 340 | 341 | ![ ](http://fabricjs.com/article_assets/4_16.png) 342 | 343 | 让我们仔细看看发生了什么。首先,通过将字体名称和路径(作为常规字体文件)作为参数传递,创建一个字体对象```new canvas.Font()```。然后通过将字体path,weight和style作为参数传递,使用```font.addFace()```添加其他字体。最后,使用```canvas.contextContainer.addFont()```(当使用**createPNGStream**或**createJPEGStream**时)或```canvas.contextTop.addFont()```(使用**toDataURL**或**toDataURLWithMultiplier**时)可以将字体添加到所需的上下文中。 344 | 345 | 现在我们可以通过将```fabric.Text```对象的**fontFamily**属性设置为字体名称来使用我们的字体。结合**fontWeight**和**fontStyle**属性,我们可以应用我们添加的字体。有关这些属性的更多信息,请参阅第2部分。请注意,该示例显示了如何在创建新文本对象时使用自定义字体,但这也适用于通过JSON加载的文本对象。 346 | 347 | 这就到了Fabric的4部分系列的结尾。我希望你现在拥有足够的知识,创造有趣,酷,有用,有趣,具有挑战性,令人兴奋的东西! -------------------------------------------------------------------------------- /part-5.md: -------------------------------------------------------------------------------- 1 | # Fabric.js介绍 第五部分 2 | 3 | 我们已经涵盖了上一系列的许多话题;从基础对象操作到动画,事件,滤镜,组合和子类。但还有几件非常有趣和有用的事情要讨论! 4 | 5 | ## 缩放和平移(Zoom and panning) 6 | 7 | 1. 让我们看看如何通过鼠标交互实现缩放和平移的基本系统。我们将使用鼠标滚轮在画布上放大20倍(2000%)并使用alt +单击动作进行拖动。 8 | 我们开始基本控制: 9 | [查看demo](http://fabricjs.com/fabric-intro-part-5#step1) 10 | 11 | 2. 这是一个基本的缩放控制,限制在1%到2000%之间。我们现在要添加画布的拖动。我们将使用ALT +拖动,但您可以更改为另一种组合。按住alt键下拉鼠标会将布尔值设为true,这样鼠标移动事件就能知道是开始拖拽了: 12 | [查看demo](http://fabricjs.com/fabric-intro-part-5#step2) 13 | 14 | 3. 好的,这是一个基本设置,可以让你控制缩放和平移。仍然有一些可能的增强。 例如,我们可以使轮缩放使画布围绕光标所在的点居中: 15 | [查看demo](http://fabricjs.com/fabric-intro-part-5#step3) 16 | 17 | 4. 最后一点,我们可以限制平移区域以避免在一个方向上无限延伸。我们描出一个1000×1000像素的矩形来表示平移区域。我们添加代码来限制边界内的移动: 18 | [查看demo](http://fabricjs.com/fabric-intro-part-5#step4) 19 | -------------------------------------------------------------------------------- /part-6.md: -------------------------------------------------------------------------------- 1 | # Fabric.js介绍 第六部分 2 | 3 | 了解转换如何在fabricJS上工作是尽可能顺利地编写应用程序的关键方面。 4 | 5 | ## 与转换相关的方法和属性 6 | 7 | 如果您计划理解和使用与自定义代码一起使用的fabricJS转换,那么这些方法应该是您最应该学习使用的方法。 8 | 一般来说,在本页面中,我们将矩阵称为6个数字的数组,表示平面上的转换,并将其作为一个类似{x: number, y: number}简单的JS对象或者是fabric.Point类的实例。(通常没有区别) 9 | 10 | ``` 11 | Canvas: 12 | - vieportTransform = matrix; 13 | Objects: 14 | - matrix = fabric.Object.prototype.calcTransformMatrix(); 15 | - matrix = fabric.Object.prototype.calcOwnMatrix(); 16 | Utils: 17 | - point = fabric.util.transformPoint(point, matrix); 18 | - matrix = fabric.util.multiplyTransformMatrices(matrix, matrix); 19 | - matrix = fabric.util.invertTransform(matrix); 20 | - options = fabric.util.qrDecompose(matrix); 21 | ``` 22 | 23 | ## 从一个空间移动到另一个空间(空间变换) 24 | 使用fabricJS常常需要与坐标和位置交互,但如果没有正确的背景,理解这些坐标的位置可能会很麻烦。 25 | 我将列出变换及其用法,然后我将尝试制作两个例子来更好地阐明发生了什么,以及如何做到这一点。 26 | 27 | Canvas.viewportTransform,将虚拟画布的一点移动到缩放和平移空间。 28 | 在cooridantes可以找到一个点,当画布不缩放和不平移时,在viewportTransfrom M中应用缩放和平移后,在位置P处的位置可以找到: 29 | 30 | ```js 31 | newP = fabric.util.transformPoint(P, canvas.viewportTransform); 32 | ``` 33 | 34 | Object.calcTransformMatrix,将表示特定时刻的通用对象转换(受顶部、左侧、缩放和许多其他属性的影响)的矩阵还原,并将点从对象空间移动到画布空间,而不是缩放。因此,给定物体空间坐标中的一个点在坐标P处,这个点将在画布上的坐标为: 35 | 36 | ```js 37 | newP = fabric.util.transformPoint(P, object.calcTransformMatrix()); 38 | ``` 39 | 40 | ## 变形顺序 41 | 渲染期间的Fabric按此顺序应用转换: 42 | 43 | ``` 44 | zoom and pan => object transformation => nested object ( group ) => nested object deeper ( nested groups ) 45 | ``` 46 | 47 | ## 恢复顺序 48 | invertTransform实用程序用于在转换逻辑中移回以进行一些反向计算: 49 | 假设您想要在画布上标记一个对象,单击鼠标,单击点。点击点P,例如元素上的10,10像素。对象被缩放和旋转,画布被缩放和平移。 50 | 要反转渲染计算,您可以遵循以下逻辑: 51 | 52 | ```js 53 | // 计算应用于对象像素的总转换: 54 | var mCanvas = canvas.viewportTransform; 55 | var mObject = object.calcTransformMatrix(); 56 | var mTotal = fabric.util.multiplyTransformMatrices(mCanvas, mObject); // 反转顺序会产生错误的结果 57 | var mInverse = fabric.util.invertTransform(mTotal); 58 | var pointInObjectPixels = fabric.util.transformPoint(pointClicked, mInverse); 59 | ``` 60 | 61 | 现在,```pointInObjectPixels```是一个位于坐标空间中的点,其中```0,0```位于对象的中心。 62 | 63 | ## 了解矩阵的效果 64 | 65 | 给定top,left,angle,scaleX,scaleY,skewX,skewY,flipX,flipY相对简单,可以创建表示该转换的矩阵。 不直接的是如何回去。矩阵有6个维度,有6个数字,而属性是7,因为我们可以按比例缩放。确实存在无数个矩阵,但是可能的属性组合的数量是一个无限大的。 66 | 现在开始使用```fabric.util.qrDecompose(matrix)```可以为我们解码矩阵。给定函数的通用可逆矩阵,它返回包含这些信息的选项对象: 67 | 68 | ```js 69 | { 70 | angle: number, // 度 71 | scaleX: number, 72 | scaleY: number, 73 | skewX: number, // 度 74 | skewY: 0, // 总是0度 75 | translateX: number, 76 | translateY: number, 77 | } 78 | ``` 79 | 80 | 这个函数给出了这个矩阵的一个可能解,将skewY约束为0。 81 | 82 | ## 一个真实的用例 83 | 84 | 一个开发人员想要将对象分组,但同时让它们自由。理想情况下,当主要对象移动时,他希望其他一些对象跟随它。 85 | 为了解释这个例子,我将调用主要对象BOSS和其他MINIONS 86 | 87 | 假设画布周围有一些物体我们可以自由移动它们。在某一点上,我们要锁定它们的相对位置和比例尺,并移动一个。当我们设置我们想要的位置时,BOSS位置由矩阵描述,正如我们到目前为止所学到的,以及每个MINIONS。 88 | 89 | 我确信它存在一个矩阵,它定义了从BOSS移动到MINIONS的必要转换,我必须找到它。 90 | 91 | ```js 92 | // 我在寻找未知关系矩阵,其中: 93 | BOSS * UNKNOW = MINION 94 | // 我向左乘以BOSS-INV 95 | BOSS-INV * BOSS * UNKNOW = BOSS-INV * MINION 96 | // BOSS-INV * BOSS = IDENTIY, 一个中立的矩阵。 97 | IDENTITY * UNKNOW = BOSS-INV * MINION 98 | // so... 99 | UNKNOW = BOSS-INV * MINION 100 | // 在fabricJS代码中等于: 101 | var minions = canvas.getObjects().filter(o => o !== boss); 102 | var bossTransform = boss.calcTransformMatrix(); 103 | var invertedBossTransform = fabric.util.invertTransform(bossTransform); 104 | minions.forEach(o => { 105 | var desiredTransform = multiply(invertedBossTransform, o.calcTransformMatrix()); 106 | // 在这里保存所需的关系。 107 | o.relationship = desiredTransform; 108 | }); 109 | ``` 110 | 111 | 好的,现在我知道如何找到关系,我可以编写一些事件处理程序来在每个BOSS操作上应用这种关系。 112 | [查看demo](http://fabricjs.com/using-transformations#bind) 113 | 114 | ```js 115 | var canvas = new fabric.Canvas('c'); 116 | var boss = new fabric.Rect( 117 | { width: 150, height: 200, fill: 'red' }); 118 | var minion1 = new fabric.Rect( 119 | { width: 40, height: 40, fill: 'blue' }); 120 | var minion2 = new fabric.Rect( 121 | { width: 40, height: 40, fill: 'blue' }); 122 | 123 | canvas.add(boss, minion1, minion2); 124 | 125 | boss.on('moving', updateMinions); 126 | boss.on('rotating', updateMinions); 127 | boss.on('scaling', updateMinions); 128 | 129 | var multiply = fabric.util.multiplyTransformMatrices; 130 | var invert = fabric.util.invertTransform; 131 | 132 | function updateMinions() { 133 | var minions = canvas.getObjects().filter(o => o !== boss); 134 | minions.forEach(o => { 135 | if (!o.relationship) { 136 | return; 137 | } 138 | var relationship = o.relationship; 139 | var newTransform = multiply( 140 | boss.calcTransformMatrix(), 141 | relationship 142 | ); 143 | opt = fabric.util.qrDecompose(newTransform); 144 | o.set({ 145 | flipX: false, 146 | flipY: false, 147 | }); 148 | o.setPositionByOrigin( 149 | { x: opt.translateX, y: opt.translateY }, 150 | 'center', 151 | 'center' 152 | ); 153 | o.set(opt); 154 | o.setCoords(); 155 | }); 156 | } 157 | 158 | document.getElementById('bind').onclick = function() { 159 | var minions = canvas.getObjects().filter(o => o !== boss); 160 | var bossTransform = boss.calcTransformMatrix(); 161 | var invertedBossTransform = invert(bossTransform); 162 | minions.forEach(o => { 163 | var desiredTransform = multiply( 164 | invertedBossTransform, 165 | o.calcTransformMatrix() 166 | ); 167 | o.relationship = desiredTransform; 168 | }); 169 | } 170 | 171 | ``` 172 | 173 | -------------------------------------------------------------------------------- /part-7.md: -------------------------------------------------------------------------------- 1 | # Fabric.js介绍 第七部分 2 | 3 | 本教程是为stackoveflow问题创建的,其中用户需要在fabricJS中使用位图而不是字体来执行文本对象。 4 | 5 | [查看demo](http://fabricjs.com/subclassing-tutorial#c) 6 | 7 | -------------------------------------------------------------------------------- /part-8.md: -------------------------------------------------------------------------------- 1 | # Fabric.js介绍 第八部分 2 | 3 | ## 新的clipPath属性 4 | 5 | 在2.4.0中,我们为所有对象引入了clipPath属性。 ClipPath将替换```clipTo: funcion() {}```,试图获得相同的灵活性但更好的兼容性。 6 | 7 | ### 如何使用 8 | 9 | 创建自己的clipPath作为普通的Fabric对象,并将其指定给要剪辑的对象的clipPath属性。 10 | 根据SVG规范中的定义,clipPath没有描边,而是充满黑色,对象中与clipPath黑色像素重叠的像素将是可见的,其他像素是不可见的。 11 | 12 | 让我们从一些基本的例子开始,让我们看看它是什么样的。 13 | 在第一个示例中,一个红色的rectable被一个圆圈夹住,只有圆圈内的部分是可见的。虽然不是很有用,但是基本的功能是这样的。 14 | 请注意:clipPath位于从物体中心开始的位置,物体的originX和originY不起任何作用,而clipPath的originX和originY则起作用,定位逻辑与```fabric.Group```相同。 15 | 16 | [查看demo](http://fabricjs.com/clippath-part1#ex1) 17 | 18 | ```js 19 | (function() { 20 | var canvas = new fabric.Canvas('ex1'); 21 | var clipPath = new fabric.Circle({ 22 | radius: 40, 23 | top: -40, 24 | left: -40 25 | }); 26 | var rect = new fabric.Rect({ 27 | width: 200, 28 | height: 100, 29 | fill: 'red' 30 | }); 31 | rect.clipPath = clipPath; 32 | canvas.add(rect); 33 | })() 34 | ``` 35 | 36 | 我们可以剪下一个组合: 37 | [查看demo](http://fabricjs.com/clippath-part1#ex2) 38 | 39 | ```js 40 | (function() { 41 | var canvas = new fabric.Canvas('ex2'); 42 | var clipPath = new fabric.Circle({ 43 | radius: 100, 44 | top: -100, 45 | left: -100 46 | }); 47 | var group = new fabric.Group([ 48 | new fabric.Rect({ width: 100, height: 100, fill: 'red' }), 49 | new fabric.Rect({ width: 100, height: 100, fill: 'yellow', left: 100 }), 50 | new fabric.Rect({ width: 100, height: 100, fill: 'blue', top: 100 }), 51 | new fabric.Rect({ width: 100, height: 100, fill: 'green', left: 100, top: 100 }) 52 | ]); 53 | group.clipPath = clipPath; 54 | canvas.add(group); 55 | })() 56 | ``` 57 | 58 | 或者我们可以用组合来剪辑。在组合的情况下,记住组合中的每个对象在逻辑上都是```或```,没有```非零```或```偶数```的剪裁规则: 59 | 60 | [查看demo](http://fabricjs.com/clippath-part1#ex3) 61 | 62 | ```js 63 | (function() { 64 | var canvas = new fabric.Canvas('ex3'); 65 | var clipPath = new fabric.Group([ 66 | new fabric.Circle({ radius: 70, top: -70, left: -70 }), 67 | new fabric.Circle({ radius: 40, left: -95, top: -95 }), 68 | new fabric.Circle({ radius: 40, left: 15, top: 15 }), 69 | ], { left: -95, top: -95 }); 70 | var group = new fabric.Group([ 71 | new fabric.Rect({ width: 100, height: 100, fill: 'red' }), 72 | new fabric.Rect({ width: 100, height: 100, fill: 'yellow', left: 100 }), 73 | new fabric.Rect({ width: 100, height: 100, fill: 'blue', top: 100 }), 74 | new fabric.Rect({ width: 100, height: 100, fill: 'green', left: 100, top: 100 }) 75 | ]); 76 | group.clipPath = clipPath; 77 | canvas.add(group); 78 | })() 79 | ``` 80 | 81 | ## 更多高级用法 82 | 83 | ### 嵌套clipPaths 84 | 85 | clipTo的一个问题和canvas.clip()的用法是你不能有多个clipPath。 86 | 通过这种实现,clippaths可以有自己的clippaths。虽然手动编程不太直观,但它允许将clipPaths交叉到一起。 87 | 88 | [查看demo](http://fabricjs.com/clippath-part2#ex4pre) 89 | 90 | ```js 91 | (function() { 92 | var canvas = new fabric.Canvas('ex4'); 93 | var clipPath = new fabric.Circle({ radius: 70, top: -50, left: -50 }); 94 | var innerClipPath = new fabric.Circle({ radius: 70, top: -90, left: -90 }); 95 | clipPath.clipPath = innerClipPath; 96 | var group = new fabric.Group([ 97 | new fabric.Rect({ width: 100, height: 100, fill: 'red' }), 98 | new fabric.Rect({ width: 100, height: 100, fill: 'yellow', left: 100 }), 99 | new fabric.Rect({ width: 100, height: 100, fill: 'blue', top: 100 }), 100 | new fabric.Rect({ width: 100, height: 100, fill: 'green', left: 100, top: 100 }) 101 | ]); 102 | group.clipPath = clipPath; 103 | canvas.add(group); 104 | })() 105 | ``` 106 | 107 | 组合内对象中的ClipPath应与组本身的clipPath隔离: 108 | 109 | [查看demo](http://fabricjs.com/clippath-part2#ex5pre) 110 | 111 | ```js 112 | (function() { 113 | var canvas = new fabric.Canvas('ex5'); 114 | var clipPath = new fabric.Circle({ radius: 100, top: -100, left: -100 }); 115 | var small = new fabric.Circle({ radius: 50, top: -50, left: -50 }); 116 | var group = new fabric.Group([ 117 | new fabric.Rect({ width: 100, height: 100, fill: 'red', clipPath: small }), 118 | new fabric.Rect({ width: 100, height: 100, fill: 'yellow', left: 100 }), 119 | new fabric.Rect({ width: 100, height: 100, fill: 'blue', top: 100 }), 120 | new fabric.Rect({ width: 100, height: 100, fill: 'green', left: 100, top: 100 }) 121 | ]); 122 | group.clipPath = clipPath; 123 | canvas.add(group); 124 | })() 125 | ``` 126 | 127 | ### 文字剪裁 128 | 129 | 用文本进行剪裁也是不可能的,开发人员通常不得不依靠模式来实现这一点: 130 | 131 | [查看demo](http://fabricjs.com/clippath-part2#ex6) 132 | 133 | ```js 134 | (function() { 135 | var canvas = new fabric.Canvas('ex6'); 136 | var clipPath = new fabric.Text( 137 | 'Hi I\'m the \nnew ClipPath!\nI hope we\'ll\nbe friends', 138 | { top: -100, left: -100 }); 139 | var group = new fabric.Group([ 140 | new fabric.Rect({ width: 100, height: 100, fill: 'red' }), 141 | new fabric.Rect({ width: 100, height: 100, fill: 'yellow', left: 100 }), 142 | new fabric.Rect({ width: 100, height: 100, fill: 'blue', top: 100 }), 143 | new fabric.Rect({ width: 100, height: 100, fill: 'green', left: 100, top: 100 }) 144 | ]); 145 | group.clipPath = clipPath; 146 | canvas.add(group); 147 | })() 148 | ``` 149 | 150 | ## 剪裁canvas 151 | 152 | 我们可以对静态画布应用clipPath,就像对对象一样。在这种情况下,clipPath受到缩放和平移的影响,与物体相反。clipPath被放置在左上角。 153 | 154 | [查看demo](http://fabricjs.com/clippath-part3#ex7pre) 155 | 156 | ```js 157 | (function() { 158 | var canvas = new fabric.Canvas('ex7'); 159 | var clipPath = new fabric.Circle({ radius: 100, top: 0, left: 50 }); 160 | var group = new fabric.Group([ 161 | new fabric.Rect({ width: 100, height: 100, fill: 'red' }), 162 | new fabric.Rect({ width: 100, height: 100, fill: 'yellow', left: 100 }), 163 | new fabric.Rect({ width: 100, height: 100, fill: 'blue', top: 100 }), 164 | new fabric.Rect({ width: 100, height: 100, fill: 'green', left: 100, top: 100 }) 165 | ]); 166 | canvas.clipPath = clipPath; 167 | canvas.add(group); 168 | })() 169 | ``` 170 | 171 | 作为旧的clipTo函数,clipPath也是剪切控件,除非你使用```canvas.controlsAboveOverlay```设置为true 172 | 173 | [查看demo](http://fabricjs.com/clippath-part3#ex8) 174 | 175 | ```js 176 | (function() { 177 | var canvas = new fabric.Canvas('ex8'); 178 | canvas.controlsAboveOverlay = true; 179 | var clipPath = new fabric.Circle({ radius: 100, top: 0, left: 50 }); 180 | var group = new fabric.Group([ 181 | new fabric.Rect({ width: 100, height: 100, fill: 'red' }), 182 | new fabric.Rect({ width: 100, height: 100, fill: 'yellow', left: 100 }), 183 | new fabric.Rect({ width: 100, height: 100, fill: 'blue', top: 100 }), 184 | new fabric.Rect({ width: 100, height: 100, fill: 'green', left: 100, top: 100 }) 185 | ]); 186 | canvas.clipPath = clipPath; 187 | canvas.add(group); 188 | })() 189 | ``` 190 | 191 | ### 动画clipPaths 192 | 193 | 剪贴簿可以像任何其他物体一样进行动画。Canvas clipPath动画非常有效,当动画对象的动画时,每次都会使对象缓存失效。 194 | 195 | [查看demo](http://fabricjs.com/clippath-part3#ex6) 196 | 197 | ```js 198 | (function() { 199 | var canvas = new fabric.Canvas('ex9'); 200 | canvas.controlsAboveOverlay = true; 201 | var clipPath = new fabric.Rect({ width: 100, height: 100, top: 0, left: 0 }); 202 | function animateLeft() { 203 | clipPath.animate({ 204 | left: 200, 205 | }, { 206 | duration: 900, 207 | onChange: canvas.requestRenderAll.bind(canvas), 208 | onComplete: animateRight, 209 | }); 210 | } 211 | function animateRight() { 212 | clipPath.animate({ 213 | left: 0, 214 | }, { 215 | duration: 1200, 216 | onChange: canvas.requestRenderAll.bind(canvas), 217 | onComplete: animateLeft, 218 | }); 219 | } 220 | function animateDown() { 221 | clipPath.animate({ 222 | top: 100, 223 | }, { 224 | duration: 500, 225 | onChange: canvas.requestRenderAll.bind(canvas), 226 | onComplete: animateUp, 227 | }); 228 | } 229 | function animateUp() { 230 | clipPath.animate({ 231 | top: 0, 232 | }, { 233 | duration: 400, 234 | onChange: canvas.requestRenderAll.bind(canvas), 235 | onComplete: animateDown, 236 | }); 237 | } 238 | var group = new fabric.Group([ 239 | new fabric.Rect({ width: 100, height: 100, fill: 'red' }), 240 | new fabric.Rect({ width: 100, height: 100, fill: 'yellow', left: 100 }), 241 | new fabric.Rect({ width: 100, height: 100, fill: 'blue', top: 100 }), 242 | new fabric.Rect({ width: 100, height: 100, fill: 'green', left: 100, top: 100 }) 243 | ], { 244 | scaleX: 1.5 245 | }); 246 | animateLeft(); 247 | animateDown(); 248 | canvas.clipPath = clipPath; 249 | canvas.add(group); 250 | })() 251 | ``` 252 | --------------------------------------------------------------------------------