Fabric.js|Fabric.js - 轻而易举操作canvas的神奇库

本文来自于Fabricjs中文版,这个站点是我经过社区允许后部署的一份国内版,希望招募同样对fabricjs感兴趣的人一起来翻译建设,在每篇文章下面都有提PR的链接,只要使用github在写编辑即可
Fabric.js|Fabric.js - 轻而易举操作canvas的神奇库
文章图片

今天 我要向你们介绍 Fabric.js — 一个能够让你轻而易举操作canvas的神奇的库. Fabric 不仅提供了一个虚拟canvas对象, 还有svg渲染器, 交互层, 还有一整套十分有用的工具. 这是一个完全开源的项目, MIT协议, 多年以来依靠许多贡献者共同维护.
Fabric 开始与2010, 在经历过原生canvas 繁琐的API操作之后. 原作者就写了一个可交互的编辑器 printio.ru — 允许用户自定义外观. 那时候只有flash app需要这种交互. 光阴似箭,经过一点点积累形成了现在的 Fabric.
让我们进一步看一看!
为什么选择fabric?
现在的Canvas 支持我们去创造一些 充满创造力 神奇的 图形 但是它提供的api实在是 水平低到令人发指. 如果我们只是想画一些简单的图形. 但是却需要一系列操作, 各种修改中心点, 如果要画一个复杂图形 — 那操作就“更有意思了”.
Fabric 的目标就是解决这些问题.
原生 canvas 方法 只允许我们使用一些简单的图形操作, 然后在画布上瞎子摸象. 想画一个矩形? 使用 fillRect(left, top, width, height). 想画一条线? 用 moveTo(left, top) 和 lineTo(x, y)组合. 这感觉就像 用画笔在画布上画画, 随着画的越来越多, 画布内容的可控性就越差.
为了避免这种低水平操作, Fabric 在基础上提供了一个简单且强大的对象模型. 更注重于画布的状态和渲染, 现在, 让我们开始学习使用 “对象”吧.
让我们通过一个简单的例子 画一个红色的矩形 来看看两者有什么不同. 这是原生 API的实现

// 获取画布的引用 var canvasEl = document.getElementById('c'); // 获取2d context 对象 用来操作 (之前提到的bitmap) var ctx = canvasEl.getContext('2d'); // 给当前上下文设置颜色 ctx.fillStyle = 'red'; // 在100, 100的位置创建一个20*20的矩形 ctx.fillRect(100, 100, 20, 20); 现在,让我们看看fabricjs怎么实现同样的效果:// 包裹一下canvas (with id="c") var canvas = new fabric.Canvas('c'); // 新建一个矩形对象 var rect = new fabric.Rect({ left: 100, top: 100, fill: 'red', width: 20, height: 20 }); // 将矩形添加到canvas里 canvas.add(rect);

Fabric.js|Fabric.js - 轻而易举操作canvas的神奇库
文章图片

目前为止, 最不同的地方在尺寸的设置 — 两个例子很像. 但是你应该也意识到两种操作思想的不同了吧. 用原生方法, 我们 操作context上下文 — 代表着整个canvas. 用 Fabric, 我们 在具体对象上操作 — 实例化它们, 修改他们的属性, 然后给它们添加到canvase上. 这些对象是fabricjs世界的第一公民.
但是画一个红色的矩形没啥难度. 我们来整点有意思的! 比如, 稍微的旋转一下?
我们试试旋转45度. 首先, 使用原生 方法:
var canvasEl = document.getElementById('c'); var ctx = canvasEl.getContext('2d'); ctx.fillStyle = 'red'; ctx.translate(100, 100); ctx.rotate(Math.PI / 180 * 45); ctx.fillRect(-10, -10, 20, 20); 接下来使用fabric:var canvas = new fabric.Canvas('c'); // create a rectangle with angle=45 var rect = new fabric.Rect({ left: 100, top: 100, fill: 'red', width: 20, height: 20, angle: 45 }); canvas.add(rect);

Fabric.js|Fabric.js - 轻而易举操作canvas的神奇库
文章图片

发生了什么?
我们只需要修改对象的's “angle” 为 45. 使用原生方法, 事情变得越来越 “有趣了”. 我们不能操作对象. 反而为了实现需求, 我们给整个 canvas bitmap旋转了 (ctx.translate, ctx.rotate). 然后在画矩形上去, 别忘了原点定位为 (-10, -10), 这样才能看起来是在(100, 100).
现在我确信你已经清楚了fabricjs存在的意义,还有他帮助我门减少了多少低级代码.
让我们再看另一个例子 — 追踪canvas状态.
假设在某一个点, 我们想要移动刚才的矩形到canvas上的另一个点? 如果不能操作对象,我们会怎么做? 是不是只能再调一遍 fillRect?
不仅如此. 在调用另一个 fillRect 时候,我们在画布上画了一个新的矩形,但是现在已经有一个了. 记得我之前提到的擦出功能吗? 为了 “移动” , 我们必须先 擦除之前的画布, 然后在新的位置画新的矩形.
var canvasEl = document.getElementById('c'); ... ctx.strokRect(100, 100, 20, 20); ...// 清除整个画布 ctx.clearRect(0, 0, canvasEl.width, canvasEl.height); ctx.fillRect(20, 50, 20, 20); 用fabric怎么实现?var canvas = new fabric.Canvas('c'); ... canvas.add(rect); ...rect.set({ left: 20, top: 50 }); canvas.renderAll();

Fabric.js|Fabric.js - 轻而易举操作canvas的神奇库
文章图片

注意最重要的区别. 用过Fabricjs, 我们不用再为了实现“移动”清除上一个画布. 只需要操作对象, 简单的修改它们的属性, 然后 re-render canvas 获取 “最新的画面”.
对象
现在我们已经了解了怎么操作 fabric.Rect 构造函数生成实例. 当然Fabric默认包含了许多基础形状 — 原, 三角, 椭圆, 等等. 这些都挂载在 fabric “变量下” 像 fabric.Circle, fabric.Triangle, fabric.Ellipse, 等等.
Fabric 提供的7中基础图形:
fabric.Circle 圆
fabric.Ellipse 椭圆
fabric.Line 线段
fabric.Polygon 多边形
fabric.Polyline 折线
fabric.Rect 矩形
fabric.Triangle 三角形
想要画一个圆? 只需要创建圆对象, 然后添加到canvas中. 和其他基础图形一样:
var circle = new fabric.Circle({ radius: 20, fill: 'green', left: 100, top: 100 }); var triangle = new fabric.Triangle({ width: 20, height: 30, fill: 'blue', left: 50, top: 50 }); canvas.add(circle, triangle);

Fabric.js|Fabric.js - 轻而易举操作canvas的神奇库
文章图片

..现在我们在100, 100有了一个绿色的原型, 50, 50有了一个蓝色的三角形.
操纵 对象
创建几何图形 — 矩形, 圆, 或者其他 — 仅仅是开始. 以后, 我们可能需要修改这些对象. 也许是某些动作触发的改变, 或播放某种动画. 或者想要在鼠标事件时修改对象某些属性 (颜色, 透明度, 尺寸, 位置).
Fabric 替我们关心了渲染和状态维护的事情. 我们只需要在对象上做手脚就行.
之前的例子展示了 set 方法 并且调用 set({ left: 20, top: 50 })从之前的位置 “移动”走了. 类似的方式, 我们可以修改任何属性. 但是有啥属性呢?
正如你期望的,与位置相关 — left, top; 尺寸相关 — width, height; 渲染相关 — fill, opacity, stroke, strokeWidth; 缩放和旋转 — scaleX, scaleY, angle; 甚至有翻转 — flipX, flipY 和倾斜 skewX, skewY
是的, 在fabric中创建翻转对象 只需要给flip*属性设置为 true.
你可通过 get method读取所有属性, 然后使用 set方法. 让我们试试修改红色矩形的属性:
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);

Fabric.js|Fabric.js - 轻而易举操作canvas的神奇库
文章图片

首先, 我们设置 “fill” 为 “red”, 将图像变成红色的了. 下一行设置了 “画笔宽度” 和 “画笔颜色” 的值, 给矩形一个5px宽的浅绿色边框. 最后, 我们修改 “angle” 和 “flipY” 属性. 注意三行不同的设置语法,都支持.
这个例子展示了 set方法的通用性. 你以后会经常用到它, 所以这个方法尽量的支持各种使用方法.
讲完了设置属性, 那么如果获取呢? 只需要使用通用的 get 方法 , 当然还有各种特殊的 get* ,来获取一个属性. 想要获取一个对象的“width”, 可以用 get('width') 或者 getWidth(). 想要获取 “scaleX” 属性 — get('scaleX') 或者 getScaleX(), 等等. 对象 “公共” 属性都有 getWidth 或 getScaleX这种方法 (“stroke”, “strokeWidth”, “angle”, 等.)
你可能注意到了,在之前的例子里 写初始化配置生成的对象和使用 set 方法创建的没什么区别.这是因为他们就是完全一样. 你可以在初始化的时候使用 “配置”, 或者在创建对象之后再使用 set 方法:
var rect = new fabric.Rect({ width: 10, height: 20, fill: '#f55', opacity: 0.7 }); // 一样的var rect = new fabric.Rect(); rect.set({ width: 10, height: 20, fill: '#f55', opacity: 0.7 });

默认配置
到这里, 你可能想问 — 不传 “配置”创建对象发生了什么. 还有那些属性吗?
当然有. Fabricjs中的对象一直会有默认属性的. 如果在创建的时候省略, 就是使用默认值. 试试看:
var rect = new fabric.Rect(); // 没有配置传入
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). 因为都是0, 所以我们看不见. 只要给宽高附上正整数后,我们就能看在一个黑色矩形在画布左上角.
Fabric.js|Fabric.js - 轻而易举操作canvas的神奇库
文章图片

Hierarchy and Inheritance
Fabric 对象并不是独立存在的.他们都继承自一个源对象
大多数对象都继承自根对象 fabric.Object. fabric.Object 代表着一个二维,有着坐标和宽高, 以及一系列其他图形特征. 这些就是之前看到的对象属性 — fill, stroke, angle, opacity, flip*, 等.
使用继承,可以让我们在fabric.Object上定义方法,然后提供给所有子类. 比如, 你想给所有对象都加一个自定义 getAngleInRadians 方法, 你可以直接在 fabric.Object.prototype上定义
fabric.Object.prototype.getAngleInRadians = function() { return this.get('angle') / 180 * Math.PI; }; var rect = new fabric.Rect({ angle: 45 }); rect.getAngleInRadians(); // 0.785...var circle = new fabric.Circle({ angle: 30, radius: 10 }); circle.getAngleInRadians(); // 0.523...circle instanceof fabric.Circle; // true circle instanceof fabric.Object; // true

正如你所见,这个方法立刻在所有实例上都生效了.
当 创建子 “类”的时候, 子类上经常要定义一些属于自己的属性和方法. 例如, fabric.Circle 需要 “radius” 属性. fabric.Image — 在下面会讲到 — 需要 getElement/setElement 方法 用于访问/设置HTML元素.
在高级项目中 使用原型来获取自定义渲染和行为非常常见.
Canvas
现在已经详细的说完了Fabricjs对象, 让我门回过头来说一下canvas.
在所有的Fabricjs例子中,你看到的第一行是不是创建canvas对象? — new fabric.Canvas('...'). fabric.Canvas 包裹着 元素, 它负责着所有Fabric.Object对象. 传入一个id, 返回 fabric.Canvas 实例.
我们可以将对象 add 进去, 通过引用关系, 也可以删除他们:
var canvas = new fabric.Canvas('c'); var rect = new fabric.Rect(); canvas.add(rect); // 添加进去canvas.item(0); //获取刚才添加的fabric.Rect canvas.getObjects(); // 获取画布中所有的对象canvas.remove(rect); // 删除fabric.Rect

所以 fabric.Canvas的主要作用就是管理对象, 它还可以写一些 配置 . 想要给画布设置背景? 对所有内容进行裁剪? 设置不同的宽/高? 是否可交互? 包括但不限于这些属性可以传给 fabric.Canvas, 同对象一样,在任何时候都可以:
var canvas = new fabric.Canvas('c', { backgroundColor: 'rgb(100,100,200)', selectionColor: 'blue', selectionLineWidth: 2 // ... }); // orvar canvas = new fabric.Canvas('c'); canvas.setBackgroundImage('http://...'); canvas.onFpsUpdate = function(){ /* ... */ }; // ...

交互功能
我们现在来说一说交互功能. 一个独特的Fabricjs功能 — 内置的 — 在对象层之上的交互层.
对象模型的存在允许以编程方式访问和操作画布上的对象. 但是对于用户来说, 需要用鼠标或者手指操作. 当你通过 new fabric.Canvas('...')初始化画布之后, 可以选择 拖动 旋转 缩放等,甚至是 群选 之后一起操作!
Fabric.js|Fabric.js - 轻而易举操作canvas的神奇库
文章图片
Fabric.js|Fabric.js - 轻而易举操作canvas的神奇库
文章图片

如果我们想让用在在画布上拖拽一些东西 — 比如一张图片 — 我们只需要创建画布,加一个图片进去. 不需要任何额外的操作.
我们可以使用把布尔值传给 Fabric's “selection” 或者对象的穿一个布尔值给对象的“selectable”字段 来控制是否可交互.
var canvas = new fabric.Canvas('c'); ... canvas.selection = false; // 关闭群选 rect.set('selectable', false); // 单个对象不可选

如果不想要这个交互功能? 你可以使用fabric.StaticCanvas 代替 fabric.Canvas . 别的都一样.
var staticCanvas = new fabric.StaticCanvas('c'); staticCanvas.add( new fabric.Rect({ width: 10, height: 20, left: 100, top: 100, fill: 'yellow', angle: 30 }));

这样创建了一个 “轻量” 版本的 canvas, 没有任何事件处理逻辑. 你还是可以操作 全部的对象模型 — 添加对象, 删除或修改他们, 或者修改 canvas 配置 — 这些都还是一如既往能用. 只是事件系统没了.
总之,如果你需要一个不需要交互的画布,就要择更轻量的 StaticCanvas 就够了.
图片
说到图片…
在画布上玩矩形圆形没什么意思,我们来试试玩图片? 正如你认为的, Fabric 也让这个变得简单. 让我们实例化一个 fabric.Image 对象然后把它添加进canvas:
(html)
Fabric.js|Fabric.js - 轻而易举操作canvas的神奇库
文章图片

(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 的实例. 并且, 我们立刻设置了图片的坐标、旋转、透明度. 加入到画布中后, 会看见一个图片在100,100 的位置, 旋转了30度, 还有轻微的透明度. 不错吧
Fabric.js|Fabric.js - 轻而易举操作canvas的神奇库
文章图片

那么, 如果文档中没有这个图片元素怎么办, 我们只是有一个url? 那么就到了 fabric.Image.fromURL派上用场的时候了
fabric.Image.fromURL('my_image.png', function(oImg) {
canvas.add(oImg);
});
看起来是不是非常简单直观? 只是调用 fabric.Image.fromURL, 传了url, 在图片加载完成之后调用一下回调函数. 回调函数的第一个默认传参就是 fabric.Image 对象. 在这时候, 你就可以像之前一样操作修改属性了, 然后加入到画布中:
fabric.Image.fromURL('my_image.png', function(oImg) { // scale image down, and flip it, before adding it onto canvas oImg.scale(0.5).set('flipX', true); canvas.add(oImg); });

路径
我们先了解了简单的图形, 然后是图片. 下面看看更复杂的图形和内容
首先看一对强力组合 — 路径 和 分组.
在Fabric中,路径代表着一个可以背修改,填充,描边的形状. 路径是由一堆命令组成的, 本质上是在模仿一支笔从一个点到另一个点. 通过 “move”, “line”, “curve”, 或者 “arc”命令, 可以组成神奇的图案. 借助路径的分组功能Paths (PathGroup's), 让用户发挥想象的空间就更大了.
Fabric中的Paths 与 SVG elements很像. 使用相同的语法, 所以可以互相转化. 稍后我们将更仔细地研究序列化和 SVG 解析, 先提醒你一下,你后几乎不会手动创建Path实例. 而你会经常使用 Fabric's 内置的 SVG 渲染器. 但是为了理解Path,我们先尝试手动创建一个简单的:
var canvas = new fabric.Canvas('c'); var path = new fabric.Path('M 0 0 L 200 100 L 170 200 z'); path.set({ left: 120, top: 120 }); canvas.add(path);

Fabric.js|Fabric.js - 轻而易举操作canvas的神奇库
文章图片

我们实例化了一个 fabric.Path 对象, 给它传了一串字符串路径指令. 虽然看起来神秘, 但是它其实很容易理解. “M” 代表 “move” 命令, 命令那只不可见的笔指在0,0的位置. “L” 代表着 “line” 用笔画到200,100的位置. 然后, 另一个 “L” 画了一条170,200的线. 最后, “z” 命令画笔闭合这条线段, 确定最终形状. 这样我们就得到了一个三角形.
显而易见 fabric.Path 只是Fabric中的另一种对象, 我们同样可以修改他的属性. 但是我们可以改的更多:
... var path = new fabric.Path('M 0 0 L 300 100 L 200 300 z'); ... path.set({ fill: 'red', stroke: 'green', opacity: 0.5 }); canvas.add(path);

Fabric.js|Fabric.js - 轻而易举操作canvas的神奇库
文章图片

出于好奇, 让我们试一试稍微复杂一点的图形. 你会发现我之前说的对,没有办法手写路径.
... var path = new fabric.Path('M121.32,0L44.58,0C36.67,0,29.5,3.22,24.31,8.41\ c-5.19,5.19-8.41,12.37-8.41,20.28c0,15.82,12.87,28.69,28.69,28.69c0,0,4.4,\ 0,7.48,0C36.66,72.78,8.4,101.04,8.4,101.04C2.98,106.45,0,113.66,0,121.32\ c0,7.66,2.98,14.87,8.4,20.29l0,0c5.42,5.42,12.62,8.4,20.28,8.4c7.66,0,14.87\ -2.98,20.29-8.4c0,0,28.26-28.25,43.66-43.66c0,3.08,0,7.48,0,7.48c0,15.82,\ 12.87,28.69,28.69,28.69c7.66,0,14.87-2.99,20.29-8.4c5.42-5.42,8.4-12.62,8.4\ -20.28l0-76.74c0-7.66-2.98-14.87-8.4-20.29C136.19,2.98,128.98,0,121.32,0z'); canvas.add(path.set({ left: 100, top: 200 }));

“M” 还是代表着 “move”, 所以画笔从 “121.32, 0” 开始. 然后 “L” 代表这画一条直线搭到 “44.58, 0”. 目前位置还能接受. “C” 命令, 代表着 “三次贝塞尔曲线”. 命令画笔从当前位置到 “36.67, 0”画一条三次贝塞尔曲线. 开始控制点为“29.5, 3.22”,结束控制点为 “24.31, 8.41”. 然后再跟上一堆的贝塞尔曲线, 最终形成了这个好看的箭头.
Fabric.js|Fabric.js - 轻而易举操作canvas的神奇库
文章图片

通常来说, 你不会直接这么“粗暴”的使用, 你可能会用到 fabric.loadSVGFromString 或 fabric.loadSVGFromURL 这类方法来加载SVG文件, 把这些工作全部交给Fabric.
说到整个 SVG 文档, Fabric的path 代表着 SVG 元素, SVG 文档中经常出现的路径集合, 在Fabric中就是组的概念 (fabric.Group 实例). 正如你想到的, 组不过是一组路径和其他对象的集合. 并且fabric.Group 继承自 fabric.Object, 所以它的添加、修改行为和其他对象一样.
就像使用路径一样,您可能不会直接使用它们. 但是一旦你需要,你应该知道它的原理.
后记
我们只是介绍了Fabric一些基础的东西. 你现在可以轻松的在canvas上操作简单的复杂的图形,图片了。 — 位置, 尺寸, 旋转, 颜色, 边框, 透明度.
该系列的下一章, 我们会讲组; 动画; 文班; SVG 解析, 渲染, 序列化; 事件; 图片滤镜等等.
与此同时, 去看看 示例 或者 基础数据 或者 别的地方, 或者直接看 文档, wiki, 和 源代码.
希望Fabric能给你带来乐趣.
【Fabric.js|Fabric.js - 轻而易举操作canvas的神奇库】阅读 Part 2.

    推荐阅读