Fabric.js是什么
Fabric.js 是一個(gè)簡化HTML5 Canvas開發(fā)的Javascript庫,F(xiàn)abric.js提供了HTML5 Canvas本身缺失的對(duì)象模型、交互層、SVG解析器以及其他一整套工具。它是一個(gè)完全開源的項(xiàng)目,在MIT下獲得授權(quán),多年來一直在維護(hù),近期要發(fā)布4.0版本,支持自定義controls。
Fabric.js能做什么
在Canvas畫布上創(chuàng)建、填充圖形(包括圖片、文字、規(guī)則圖形和復(fù)雜路徑組成圖形)。
給圖形填充漸變顏色。
組合圖形(包括組合圖形、圖形文字、圖片等)。
設(shè)置圖形動(dòng)畫及用戶交互,生成的對(duì)象自帶拖拉拽功能。
JSON, SVG數(shù)據(jù)的序列化和反序列化。
為什么使用Fabric.js
HTML5 Canvas提供了完整的畫布,可以輕松的在畫布上繪制簡單的圖形、制作簡單的動(dòng)畫,但是HTML5 Canvas提供的API過于低級(jí),且操作基于上下文,因此在繪制復(fù)雜圖形或者需要實(shí)現(xiàn)用戶交互時(shí),就顯得不是那么方便了。Fabric.js在HTML5 Canvas原生API之上,提供完整的對(duì)象模型,由Fabric.js來管理HTML5 Canvas畫布的狀態(tài)和渲染,用戶基于具體的對(duì)象,來編寫代碼,完成所需功能的開發(fā)(類似于面向過程和面向?qū)ο螅!癟alk is cheap. Show me the code”,下面通過代碼來看看用HTML5 Canvas原生方法和用Fabric.js分別實(shí)現(xiàn)在畫布上畫一個(gè)矩形這一功能。
用HTML5 Canvas的原生方法實(shí)現(xiàn)
// 獲取canvas元素
var canvasEl = document.getElementById('c');
// 獲取上下文
var ctx = canvasEl.getContext('2d');
// 設(shè)置填充顏色
ctx.fillStyle = 'red';
// 在100,100點(diǎn)創(chuàng)建20x20的矩形
ctx.fillRect(100, 100, 20, 20);
在線演示地址:https://codepen.io/liblack/pen/LYpaMwa
用Fabric.js實(shí)現(xiàn)相同的功能:
// 創(chuàng)建原生canvas元素的包裝類(‘c’canvas元素的id)
var canvas = new fabric.Canvas('c');
// 創(chuàng)建一個(gè)矩形對(duì)象
var rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'red',
width: 20,
height: 20
});
// 添加矩形到畫布上
canvas.add(rect);
在線演示地址:https://codepen.io/liblack/pen/PoPLVwZ
由上面例子,可以看到使用HTML5 Canvas原生方法是對(duì)context(代表整個(gè)畫布位圖的對(duì)象)進(jìn)行操作,而使用Fabric.js,我們是對(duì)對(duì)象操作,只需要去實(shí)例化對(duì)象,設(shè)置對(duì)象的屬性,然后將它們添加到canvas中。到這里,可能還感受不到Fabric對(duì)比原生Canvas方法的優(yōu)勢(shì),下面可以完成這樣的功能,即在剛才繪制的矩形的基礎(chǔ)上,將矩形位置移動(dòng)到(10,10)點(diǎn)上。
這樣的功能,在Canvas原生方法上實(shí)現(xiàn),需要先把原來的矩形從畫布上清除,然后在(10,10)位置重新繪制一個(gè)矩形,以此來實(shí)現(xiàn)矩形移動(dòng)這樣的功能。
// 擦除之前的矩形(這里是擦除了整個(gè)canvas區(qū)域)
ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
// 重新繪制矩形
ctx.fillRect(20, 50, 20, 20);
聽起來是不是很蹩腳?是的,使用Canvas原生方法時(shí),就像是個(gè)帶橡皮檫的筆刷,在畫布上繪制圖形時(shí),是筆刷在畫布上移動(dòng),繪制出的圖形就是筆刷移動(dòng)的整個(gè)痕跡,而想把繪制好的圖形移動(dòng)到其他地方,只好用橡皮檫擦掉,然后在新位置重新繪制。
相應(yīng)的,使用Fabric.js實(shí)現(xiàn)該功能時(shí),只需要改變矩形對(duì)象的屬性,然后重新刷新(渲染)畫布即可。
rect.set({ left: 20, top: 50 });
canvas.renderAll();
Canvas對(duì)象
fabric.Canvas作為元素的包裝器,創(chuàng)建fabric.Canvas對(duì)象如下:
var canvas = new fabric.Canvas('...')
它接收一個(gè)元素的id,并返回一個(gè)fabric.Canvas 的實(shí)例。fabric.Canvas對(duì)象,并不是DOM里的元素,如果需要直接控制DOM,或者對(duì)應(yīng)的context,需要通過相應(yīng)API去獲取。
var canvasElement = document.getElementById(canvasEle);
var ctx = canvasElement.getContext("2d");
fabric.Canvas對(duì)象負(fù)責(zé)管理畫布上繪制的所有對(duì)象,可以將對(duì)象添加到fabric.Canvas對(duì)象,也可以從fabric.Canvas獲取或刪除對(duì)象。
var canvas = new fabric.Canvas('c');
var rect = new fabric.Rect();
canvas.add(rect); // 添加對(duì)象
canvas.item(0); ///獲取之前添加的fabric.Rect(第一個(gè)對(duì)象)
canvas.getObjects(); ///獲取畫布上的所有對(duì)象(rect將是第一個(gè)也是唯一一個(gè))
canvas.remove(rect); ///刪除之前添加的對(duì)象刪除
fabric.Canvas對(duì)象可以對(duì)畫布進(jìn)行配置。比如需要為整個(gè)畫布設(shè)置背景顏色或圖像?需要將所有內(nèi)容剪裁到特定區(qū)域?設(shè)置不同的寬度/高度?指定畫布是否是交互式的?所有這些選項(xiàng)(及其他)都可以在fabric.Canvas對(duì)象上進(jìn)行設(shè)置,可以在創(chuàng)建時(shí)或之后進(jìn)行設(shè)置。
var canvas = new fabric.Canvas('c', {
backgroundColor: 'rgb(100,100,100,200)',
selectionColor:'blue',
selectLineWidth: 2
// ...
});
// 或
var canvas = new fabric.Canvas('c');
canvas.setBackgroundImage('http://...');
canvas.onFpsupdate = function(){ /* ...... */ };
// ...
注意這種創(chuàng)建對(duì)象的形式,F(xiàn)abric.js里基本上都是類似的,類名表示要?jiǎng)?chuàng)建的對(duì)象類型,第一個(gè)參數(shù)是必要的數(shù)據(jù),第二個(gè)參數(shù)是各種選項(xiàng)。
所有對(duì)canvas的修改,包括在其中添加刪除對(duì)象,以及對(duì)象參數(shù)的修改,都需要調(diào)用渲染方法才能顯示出來:
canvas.renderAll();
Objects基本圖形
Fabric.js提供了7種基本圖形,下面是圖形對(duì)應(yīng)的類:
fabric.Circle
fabric.Ellipse
fabric.Line
fabric.Polygon
fabric.Polyline
fabric.Rect
fabric.Triangle
所有基本形狀,都可以通過類實(shí)例的屬性頭椒ɡ純刂撲塹奈恢謾⒀丈⒋笮〉妊健K欣嘍技?jí)瘍丛fabric.Object類,有一些公共的屬性和方法。
創(chuàng)建
下面是畫線的例子(給出兩個(gè)頂點(diǎn)坐標(biāo)):
var line = new fabric.Line([x1, y1, x2, y2], {
strokeWidth: 2, //線寬
stroke: rgba(255,0,0,0.8), //線的顏色
selectable: false
});
canvas.add(line);
畫圓的例子(頂點(diǎn)和半徑是在選項(xiàng)里的),這里left和top其實(shí)就是(x,y),應(yīng)該是借用了css里的命名。
var circle = new fabric.Circle({
radius: 2,
left: left,
top: top,
originX: 'center',
originY: 'center',
fill: rgba(0,200,0,0.8),
strokeWidth: 1,
stroke: rgba(255,0,0,0.8),
selectable: false
});
canvas.add(circle);
從這里可以看出,和創(chuàng)建canvas類似,第一個(gè)參數(shù)是這個(gè)類專用的(比如畫直線的時(shí)候傳的起止點(diǎn)坐標(biāo)),第二個(gè)參數(shù)是通用選項(xiàng),如果沒有專用參數(shù),那么第一個(gè)參數(shù)就直接是通用選項(xiàng)。所有創(chuàng)建完的形狀,只有通過canvas.add方法加入,才能顯示出來。
控制
left和top是每種Object都有的屬性,至于它到底指圖形中哪一個(gè)點(diǎn)的坐標(biāo),由originX和originY這組參數(shù)決定,它們相當(dāng)于文本編輯軟件里的對(duì)齊方式,originX有三種可選值:left,center, right;originY也有三種可選值:top, center, bottom。
它們的示意圖如下:
http://fabricjs.com/test/misc/origin.html
如果希望對(duì)象的默認(rèn)原點(diǎn)在中心,可以這樣設(shè)置:
fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center'
width和height也是可以直接存取的屬性,顧名思義,表示長和寬(所有形狀都是有外接矩形的,所以可以用長和寬來控制大小)。除了上面那幾個(gè)可以直接存取的屬性,大部分屬性需要使用get/set方法讀寫,比如:
line.left = pointer.x;
line.top = pointer.y;
line.set('stroke', startColor);
line.set('height', 20);
Image對(duì)象
Image跟其他形狀類似,都是fabric.Object的子類,最大的區(qū)別在于,圖像文件的加載是異步的,所以對(duì)Image的后續(xù)操作,都要在回調(diào)中完成。
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);
});
Path對(duì)象
在Fabric.js中的Path代表了一個(gè)形狀的輪廓,它可以被填充、描邊和修改。Path由一系列的命令組成,本質(zhì)上是模仿一支筆從一個(gè)點(diǎn)到另一個(gè)點(diǎn)。在“move”, “l(fā)ine”, “curve”, or “arc”等命令的幫助下,Path可以形成非常復(fù)雜的形狀。而在Paths(PathGroup’s)組的幫助下,Path的能實(shí)現(xiàn)更多的復(fù)雜圖形。
Fabric.js中的Path與SVG的 元素非常相似。它們使用相同的命令集,可以從 元素中創(chuàng)建,并將其序列化。我們稍后會(huì)更深入地研究序列化和SVG解析,但現(xiàn)在值得一提的是,實(shí)踐中很少會(huì)手動(dòng)創(chuàng)建Path實(shí)例。相反,更多的是使用Fabric.js的內(nèi)置SVG解析器。
手工創(chuàng)建一個(gè)簡單的Path對(duì)象。
var path = new fabric.Path('M 0 0 L 200 100 L 170 200 z');
path.set({ left: 120, top: 120 });
canvas.add(path);
實(shí)例化 fabric.Path 對(duì)象,傳遞給它一串路徑指令。雖然看起來很隱蔽,但實(shí)際上很容易理解。”M “代表 “移動(dòng) “指令,告訴隱形筆要移動(dòng)到0,0點(diǎn)。”L “代表 “線”,讓筆畫一條線到200,100點(diǎn)。然后,另一個(gè) “L “代表 “線”,讓筆畫一條線到170,200點(diǎn)。最后,”z “告訴畫筆關(guān)閉當(dāng)前路徑并最終確定形狀。結(jié)果,得到的是一個(gè)三角形的形狀。
由于 fabric.Path 就像 Fabric 中的其他對(duì)象一樣,我們還可以改變它的一些屬性。但我們還可以對(duì)它進(jìn)行更多的修改。
...
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);
實(shí)踐中,更多是直接使用fabric.loadSVGfromString 或 fabric.loadSVGfromURL 方法來加載整個(gè) SVG 文件,然后讓 Fabric 的 SVG 解析器來完成所有 SVG 元素的解析工作,并創(chuàng)建相應(yīng)的 Path 對(duì)象。
Events事件
事件驅(qū)動(dòng)的架構(gòu)是一個(gè)框架靈活性和可擴(kuò)展性的基礎(chǔ)。Fabric.js提供了一個(gè)廣泛的事件系統(tǒng),覆蓋了低級(jí)的 “鼠標(biāo)”事件到高級(jí)的對(duì)象事件。
這些事件使我們能夠監(jiān)控到到畫布上發(fā)生的各種動(dòng)作的不同時(shí)刻。比如想知道鼠標(biāo)被按下的時(shí)間,只需監(jiān)聽”mouse:down”事件就可以了。想知道對(duì)象何時(shí)被添加到畫布上,只需要監(jiān)聽”object:added “事件就在那里。
事件API非常簡單,類似于jQuery、Underscore.js或其他流行的JS庫。有on方法來初始化事件監(jiān)聽器,有off方法來移除事件監(jiān)聽器。
下面一個(gè)實(shí)際的例子。
var canvas = new fabric.Canvas('...');
canvas.on('mouse:down', function(options) {
console.log(options.e.clientX, options.e.clientY);
});
上面例子演示了在canvas上添加”mouse:down”事件監(jiān)聽器,并給它設(shè)置事件處理程序,它將記錄事件發(fā)生的坐標(biāo)。事件處理程序會(huì)接收一個(gè)選項(xiàng)對(duì)象,它有兩個(gè)屬性:e—原始事件,和target—畫布上的點(diǎn)擊對(duì)象(如果有的話)。該事件在任何時(shí)候都是存在的,但目標(biāo)只有在實(shí)際點(diǎn)擊了畫布上的某個(gè)對(duì)象后才會(huì)存在。target也只有在有意義的情況下才會(huì)傳遞給事件的處理程序,例如,對(duì)于 “mouse:down”事件,但對(duì)于 “after:render”事件(表示整個(gè)畫布被重新繪制),target不會(huì)傳遞給事件處理程序。
canvas.on('mouse:down', function(options) {
if (options.target) {
console.log('一個(gè)對(duì)象被點(diǎn)擊了!', options.target.type);
}
});
如果你點(diǎn)擊了一個(gè)對(duì)象,上面的例子會(huì)輸出 “一個(gè)對(duì)象被點(diǎn)擊了!”。還會(huì)顯示被點(diǎn)擊的對(duì)象類型。
那么,在Fabric.js中還有鼠標(biāo)級(jí)的事件:“mouse:down”, “mouse:move”, 和”mouse:up”。通用的事件: “after:render”。選擇相關(guān)的事件: “before:selection:cleared”,”selection:create”,”selection:cleared”。對(duì)象相關(guān)的事件: “object:moded”、”object:selected”、”object:moving”、”object:scaling”、”object:rotating”、”object:additional “和 “object:detter”。
注意,像”object:moving”(或”object:scaling”)這樣的事件在每次對(duì)象移動(dòng)(或縮放)時(shí),即使是一個(gè)像素點(diǎn)的移動(dòng),也會(huì)連續(xù)地被觸發(fā)。另一方面,像 “object:modified” 或 “selection:create”這樣的事件只在操作(對(duì)象修改或選區(qū)創(chuàng)建)結(jié)束時(shí)才會(huì)被觸發(fā)。如果將事件直接附加到畫布上的(canvas.on(‘mouse:down’, …)),這意味著事件被覆蓋到了canvas實(shí)例上。如果一個(gè)頁面上有多個(gè)畫布,可以給每個(gè)畫布附加不同的事件監(jiān)聽器。它們都是獨(dú)立互不影響的。
為了方便,F(xiàn)abric.js將事件系統(tǒng)做得更進(jìn)一步,允許直接將監(jiān)聽器附加到具體對(duì)象上。如下例子。
var rect = new fabric.Rect({ width: 100, height: 50, fill: 'green' });
rect.on('elected', function() {
console.log('選擇了一個(gè)矩形');
});
var circle = new fabric.Circle({ radius: 75, fill: 'blue' });
circle.on('selected', function() {
console.log('選擇了一個(gè)圓圈');
});
上述例子直接給矩形和圓的實(shí)例附加事件監(jiān)聽器,使用的是 “selected “事件,而不是 “object:selected”。同樣的,可以在對(duì)象上使用 “modified”事件(當(dāng)附加到canvas上時(shí)使用 “object:modified”)、”rotating”事件(當(dāng)附加到canvas時(shí)使用 “object:rotating”)等等。
Groups組
fabric.Group是Fabric.js提供的強(qiáng)大的功能之一。使用Groups可以將任何Fabric對(duì)象組合成一個(gè)單一實(shí)體,這樣就能夠?qū)⑦@些對(duì)象作為一個(gè)單一的單元來處理。用鼠標(biāo)將畫布上的任意數(shù)量的Fabric對(duì)象進(jìn)行組合,一旦組合后,這些對(duì)象都可以一起移動(dòng)甚至修改。它們組成了一個(gè)組。我們可以對(duì)該組進(jìn)行縮放、旋轉(zhuǎn),甚至改變其呈現(xiàn)屬性—顏色、透明度、邊框等。
下面創(chuàng)建一個(gè)由2個(gè)對(duì)象組成的組,即圓圈和文本。
var circle = new fabric.Circle({
radius: 100,
fill: '#eef',
scaleY: 0.5,
originX: 'center',
originY: 'center'
});
var text = new fabric.Text('hello world', {
fontSize: 30,
originX: 'center',
originY: 'center'
});
var group = new fabric.Group([ circle, text ], {
left: 150,
top: 100,
angle: -10
});
canvas.add(group);
組成組以后,依舊可以對(duì)每個(gè)對(duì)象操作,改變對(duì)象的屬性和狀態(tài)。
group.item(0).set('fill', 'red');
group.item(1).set({
text: 'trololo',
fill: 'white'
});
Serialization序列化
為構(gòu)建某種有狀態(tài)的應(yīng)用程序,允許用戶將canvas內(nèi)容的結(jié)果保存在服務(wù)器上,或者將內(nèi)容流媒體化到不同的客戶端。Fabric.js提供了canvas序列化/解序列化支持。
toObject, toJSON
Fabric中序列化的主要方法是 fabric.Canvas#toObject()和 fabric.Canvas#toJSON()方法。讓我們來看一個(gè)簡單的例子,首先對(duì)一個(gè)空畫布進(jìn)行序列化。
var canvas = new fabric.Canvas('c');
JSON.stringify(canvas); // '{"objects":[], "background": "rgba(0,0,0,0,0)"}'
使用的是ES5 JSON.stringify()方法,如果傳入的對(duì)象存在toJSON方法,那么這個(gè)方法就會(huì)隱含地調(diào)用toJSON方法。由于Fabric中的canvas實(shí)例有toJSON方法,所以調(diào)用JSON.stringify(canvas)方法和調(diào)用JSON.stringify(canvas.toJSON())一樣。
注意,返回的字符串表示空的canvas。它是JSON格式的,本質(zhì)上由 “objects”和”background”屬性組成。”objects”目前是空的,因?yàn)閏anvas上沒有任何東西,而 background 有一個(gè)默認(rèn)的透明值(“rgba(0,0,0,0,0)”)。
當(dāng)在canvas上添加了具體對(duì)象后:
canvas.add(new fabric.Circle({
left: 100,
top: 100,
radius: 50,
fill: 'red'
}));
console.log(JSON.stringify(canvas));
序列化后結(jié)果如下:
'{"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)"}'
toSvg
Fabric.js支持將canvas畫布序列化為SVG格式的文本。
canvas.add(new fabric.Rect({
left: 50,
top: 50,
height: 20,
width: 20,
fill: 'green'
}));
console.log(canvas.toSVG());
序列化結(jié)果如下:
<?xml version="1.0" standalone="no" ?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800" height="700" xml:space="preserve">
<desc>created with Fabric.js 0.9.21</desc>
<rect x="-10" y="-10" rx="0" ry="0" width="20" height="20" style="stroke: none; stroke-width: 1; stroke-dasharray: ; fill: green; opacity: 1;" transform="translate(50 50)" />
</svg>
Deserialization反序列化, SVG Parser SVG解析器
與序列化類似,反序列化是從字符串中加載canvas,與序列化時(shí)相對(duì)應(yīng)的,也有兩種方法:從JSON文本反序列和從SVG文本反序列化。當(dāng)使用JSON表示時(shí),有 fabric.Canvas#loadfromJSON和 fabric.Canvas#loadfromDatalessJSON方法。當(dāng)使用SVG時(shí),
有 fabric.loadSVGfromURL和 fabric.loadSVGfromString兩個(gè)方法。
var canvas = new fabric.Canvas();
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)"}');
下移:canvas.sendBackwards(obj)
上移:canvas.bringForward(obj)
置頂:canvas.bringToFront(obj)
置底:canvas.sendToBack(obj)
更多資源
fabric.js官網(wǎng):http://fabricjs.com/
fabric.js源碼:https://github.com/fabricjs/fabric.js
fabric.js應(yīng)用案例:https://printio.ru/tees/new_v2
HTML5 Canvas資料:https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial
該文章在 2023/5/23 11:21:46 編輯過