Fabric.js 簡(jiǎn)單介紹和使用
當(dāng)前位置:點(diǎn)晴教程→知識(shí)管理交流
→『 技術(shù)文檔交流 』
簡(jiǎn)介Fabric.js是一個(gè)可以簡(jiǎn)化canvas程序編寫(xiě)的庫(kù)。 Fabric.js為canvas提供所缺少的對(duì)象模型, svg parser, 交互和一整套其他不可或缺的工具?;贛IT協(xié)議開(kāi)源,在github上有許多人貢獻(xiàn)代碼。 Why fabric?canvas提供一個(gè)好的畫(huà)布能力, 但其api超級(jí)爛。如果你就想畫(huà)個(gè)簡(jiǎn)單圖形, 其實(shí)也可以, 不過(guò)做一些復(fù)雜的圖形繪制, 編寫(xiě)一些復(fù)雜的效果,就不是那么好了。 用對(duì)象的方式去編寫(xiě)代碼 舉個(gè)例子 // reference canvas element (with id="c") var canvasEl = document.getElementById('c'); // get 2d context to draw on (the "bitmap" mentioned earlier) var ctx = canvasEl.getContext('2d'); // set fill color of context ctx.fillStyle = 'red'; // create rectangle at a 100,100 point, with 20x20 dimensions ctx.fillRect(100, 100, 20, 20); 使用fabric // create a wrapper around native canvas element (with id="c") var canvas = new fabric.Canvas('c');
// create a rectangle object var rect = new fabric.Rect({ left: 100, top: 100, fill: 'red', width: 20, height: 20 });
// "add" rectangle onto canvas canvas.add(rect); 好的 其實(shí)并沒(méi)有什么差別 不過(guò)我們?cè)囍D(zhuǎn)一下角度 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); 如果我們想重新調(diào)整位置 怎么辦 var canvasEl = document.getElementById('c'); ... ctx.strokRect(100, 100, 20, 20); ... // erase entire canvas area ctx.clearRect(0, 0, canvasEl.width, canvasEl.height); ctx.fillRect(20, 50, 20, 20); fabric var canvas = new fabric.Canvas('c'); ... canvas.add(rect); ... rect.set({ left: 20, top: 50 }); canvas.renderAll(); objects
畫(huà)一個(gè)三角形 和一個(gè) 圓形 // create a wrapper around native canvas element (with id="c") var canvas = new fabric.Canvas('c'); 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); Manipulating objects可以簡(jiǎn)單的使用set來(lái)控制對(duì)象屬性 rect.set('fill', 'red'); rect.set({ strokeWidth: 5, stroke: 'rgba(100,200,200,0.5)' }); rect.set('angle', 15).set('flipY', true); 有了set 其實(shí)也就有了get,對(duì)象可以創(chuàng)建時(shí)設(shè)置屬性,也可以先實(shí)例化再賦值。 var rect = new fabric.Rect({ width: 10, height: 20, fill: '#f55', opacity: 0.7 }); // or functionally identical var rect = new fabric.Rect(); rect.set({ width: 10, height: 20, fill: '#f55', opacity: 0.7 }); 另外這里的fabric.Rect是函數(shù),大家可以使用class繼承。 默認(rèn)值var rect = new fabric.Rect(); // notice no options passed in rect.getWidth(); // 0 rect.getHeight(); // 0 rect.getLeft(); // 0 rect.getTop(); // 0 rect.getFill(); // rgb(0,0,0) rect.getStroke(); // null rect.getOpacity(); // 1 Hierarchy and Inheritancefabric.Object 是圖像基類(lèi) 你可以自己擴(kuò)充方法 fabric.Object.prototype.getAngleInRadians = function() { return this.getAngle() / 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 Canvasfabric.Canvas 是canvas的wrapper var canvas = new fabric.Canvas('c'); var rect = new fabric.Rect(); canvas.add(rect); // add object canvas.item(0); // reference fabric.Rect added earlier (first object) canvas.getObjects(); // get all objects on canvas (rect will be first and only) canvas.remove(rect); // remove previously-added fabric.Rect 經(jīng)典的設(shè)計(jì) 有options 有對(duì)象方法 var canvas = new fabric.Canvas('c', { backgroundColor: 'rgb(100,100,200)', selectionColor: 'blue', selectionLineWidth: 2 // ... }); // or var canvas = new fabric.Canvas('c'); canvas.setBackgroundImage(http://...'); canvas.onFpsupdate = function(){ /* ... */ }; // ... Images使用fabric.Image你可以輕松的加載一個(gè)圖片 <canvas id="c"></canvas> <img src="my_image.png" id="my-image"> js var canvas = new fabric.Canvas('c'); var imgElement = document.getElementById('my-image'); var imgInstance = new fabric.Image(imgElement, { left: 100, top: 100, angle: 30, opacity: 0.85 }); canvas.add(imgInstance); 當(dāng)然也可以通過(guò)url加載一張圖片到canvas fabric.Image.fromURL('my_image.png', function(oImg) { canvas.add(oImg); }); 可以對(duì)加載的圖片進(jìn)行預(yù)處理 fabric.Image.fromURL('my_image.png', function(oImg) { // scale image down, and flip it, before adding it onto canvas oImg.scale(0.5).setFlipX(true); canvas.add(oImg); }); Path and PathGroup我們已經(jīng)看了簡(jiǎn)單的形狀,然后圖像。更復(fù)雜、豐富的形狀和內(nèi)容呢? 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); “M” 代表 “move” 命令, 告訴筆到 0, 0 點(diǎ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); path也可以設(shè)置canvas屬性 當(dāng)然,太困然了,所以你可以使用 fabric.loadSVGfromString or fabric.loadSVGfromURL 方法。 Afterword看些demo吧。 上面我們學(xué)習(xí)了基礎(chǔ)用法,現(xiàn)在我們開(kāi)始一些好玩的。 Animation我們先回顧設(shè)置一下正方形角度方法 rect.set('angle', 45); 這是沒(méi)有動(dòng)畫(huà)的 Fabric object都有animate方法 rect.animate('angle', 45, { onChange: canvas.renderAll.bind(canvas) }); 那么正方形會(huì)從0到45有一個(gè)動(dòng)畫(huà)過(guò)度 從左到右進(jìn)行變動(dòng) rect.animate('left', '+=100', { onChange: canvas.renderAll.bind(canvas) }); 逆時(shí)針轉(zhuǎn)5度 rect.animate('angle', '-=5', { onChange: canvas.renderAll.bind(canvas) }); 當(dāng)然animate還支持這些方法
rect.animate('left', '+=100', { onChange: canvas.renderAll.bind(canvas), duration: 3000, easing: fabric.util.ease.easeOutBounce }); Image filters圖片可以使用filter效果 fabric.Image.fromURL('pug.jpg', function(img) { // add filter img.filters.push(new fabric.Image.filters.Grayscale()); // apply filters and re-render canvas when done img.applyFilters(canvas.renderAll.bind(canvas)); // add image onto canvas canvas.add(img); }); filter一次可以使用多個(gè)效果 當(dāng)然 你也可以自己定義filter fabric.Image.filters.Redify = fabric.util.createClass({ type: 'Redify', applyTo: function(canvasEl) { var context = canvasEl.getContext('2d'), imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), data = imageData.data; for (var i = 0, len = data.length; i < len; i += 4) { data[i + 1] = 0; data[i + 2] = 0; } context.putImageData(imageData, 0, 0); } }); fabric.Image.filters.Redify.fromObject = function(object) { return new fabric.Image.filters.Redify(object); }; Colorsfabric 支持 hex rgb rgba顏色 new fabric.Color('#f55'); new fabric.Color('#123123'); new fabric.Color('356735'); new fabric.Color('rgb(100,0,100)'); new fabric.Color('rgba(10, 20, 30, 0.5)'); 并且支持相互轉(zhuǎn)換 new fabric.Color('#f55').toRgb(); // "rgb(255,85,85)" new fabric.Color('rgb(100,100,100)').toHex(); // "646464" new fabric.Color('fff').toHex(); // "FFFFFF" 兩種顏色可以疊加 并且你可以使用一些特定效果 var redish = new fabric.Color('#f55'); var greenish = new fabric.Color('#5f5'); redish.overlayWith(greenish).toHex(); // "AAAA55" redish.toGrayscale().toHex(); // "A1A1A1" Gradients可以使用漸變色 var circle = new fabric.Circle({ left: 100, top: 100, radius: 50}); circle.setGradient('fill', { x1: 0, y1: -circle.height / 2, x2: 0, y2: circle.height / 2, colorStops: { 0: '#000', 1: '#fff' } }); 首先確定兩個(gè)點(diǎn) 在其距離中以百分比定位顏色 circle.setGradient('fill', { x1: -circle.width / 2, y1: 0, x2: circle.width / 2, y2: 0, colorStops: { 0: "red", 0.2: "orange", 0.4: "yellow", 0.6: "green", 0.8: "blue", 1: "purple" } }); Text
如何添加文字 var text = new fabric.Text('hello world', { left: 100, top: 100 }); canvas.add(text); fontFamily var comicSansText = new fabric.Text("I'm in Comic Sans", {fontFamily: 'Comic Sans'}); fontSize var text40 = new fabric.Text("I'm at fontSize 40", {fontSize: 40}); var text20 = new fabric.Text("I'm at fontSize 20", {fontSize: 20}); fontWeight var normalText = new fabric.Text("I'm a normal text", {fontWeight: 'normal'}); var boldText = new fabric.Text("I'm at bold text", {fontWeight: 'bold'}); textDecoration var underlineText = new fabric.Text("I'm an underlined text", {textDecoration: 'underline'}); var strokeThroughText = new fabric.Text("I'm a stroke-through text", {textDecoration: 'line-through'}); var overlineText = new fabric.Text("I'm an overline text", {textDecoration: 'overline'}); shadow var shadowText1 = new fabric.Text("I'm a text with shadow", { shadow: 'rgba(0,0,0,0.3) 5px 5px 5px'}); var shadowText2 = new fabric.Text("And another shadow", { shadow: 'rgba(0,0,0,0.2) 0 0 5px'}); var shadowText3 = new fabric.Text("Lorem ipsum dolor sit", { shadow: 'green -5px -5px 3px'}); fontStyle var italicText = new fabric.Text("A very fancy italic text", { fontStyle: 'italic', fontFamily: 'Delicious'}); var anotherItalicText = new fabric.Text("another italic text", { fontStyle: 'italic', fontFamily: 'Hoefler Text'}); stroke & strokeWidth var textWithStroke = new fabric.Text("Text with a stroke", { stroke: '#ff1318', strokeWidth: 1}); var loremIpsumDolor = new fabric.Text("Lorem ipsum dolor", { fontFamily: 'Impact', stroke: '#c3bfbf', strokeWidth: 3}); textAlign var text = 'this is\na multiline\ntext\naligned right!'; var alignedRightText = new fabric.Text(text, { textAlign: 'right'}); lineHeight var lineHeight3 = new fabric.Text('Lorem ipsum ...', { lineHeight: 3}); var lineHeight1 = new fabric.Text('Lorem ipsum ...', { lineHeight: 1}); textBackgroundColor var text = 'this is\na multiline\ntext\nwith\ncustom lineheight\n&background'; var textWithBackground = new fabric.Text(text, { textBackgroundColor: 'rgb(0,200,0)'}); Events怎么可以沒(méi)有事件呢 事件以on off使用 canvas 可以捕捉事件 mouseevent renderevent selectionevent objectevent var canvas = new fabric.Canvas('...'); canvas.on('mouse:down', function(options) { console.log(options.e.clientX, options.e.clientY); }); 同樣這些事件也可以用任何fabric對(duì)象監(jiān)聽(tīng) var rect = new fabric.Rect({ width: 100, height: 50, fill: 'green' }); rect.on('selected', function() { console.log('selected a rectangle'); }); var circle = new fabric.Circle({ radius: 75, fill: 'blue' }); circle.on('selected', function() { console.log('selected a circle'); }); 上面我們學(xué)習(xí)了基礎(chǔ)和高級(jí)特性,現(xiàn)在介紹更神奇的東西。 Groups話說(shuō)這個(gè)功能我最喜歡,組成群組可以統(tǒng)一修改其中所有組件屬性,如何定義: 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); 現(xiàn)在我們就可以對(duì)其中的對(duì)象集修改 group.item(0).setFill('red'); group.item(1).set({ text: 'trololo', fill: 'white' }); group中的元素相對(duì)于group定位 但是由于要確保之前得到卻切位置 所以要異步 fabric.Image.fromURL('/assets/pug.jpg', function(img) { var img1 = img.scale(0.1).set({ left: 100, top: 100 }); fabric.Image.fromURL('/assets/pug.jpg', function(img) { var img2 = img.scale(0.1).set({ left: 175, top: 175 }); fabric.Image.fromURL('/assets/pug.jpg', function(img) { var img3 = img.scale(0.1).set({ left: 250, top: 250 }); canvas.add(new fabric.Group([ img1, img2, img3], { left: 200, top: 200 })) }); }); }); group 可以動(dòng)態(tài)添加 group.add(new fabric.Rect({ ... originX: 'center', originY: 'center' })); 添加并修改group group.addWithupdate(new fabric.Rect({ ... left: group.getLeft(), top: group.getTop(), originX: 'center', originY: 'center' })); 當(dāng)然 你可以使用canvas上已有的進(jìn)行克隆 組合 // create a group with copies of existing (2) objects var group = new fabric.Group([ canvas.item(0).clone(), canvas.item(1).clone() ]); // remove all objects and re-render canvas.clear().renderAll(); // add group onto canvas canvas.add(group); Serialization序列化是為了相互傳輸 toObject, toJSONcanvas 實(shí)現(xiàn)了toJSON接口 可以被序列化 var canvas = new fabric.Canvas('c'); JSON.stringify(canvas); // '{"objects":[],"background":"rgba(0, 0, 0, 0)"}' canvas 可以隨時(shí)被修改 json數(shù)據(jù)會(huì)被修改 canvas.backgroundColor = 'red'; JSON.stringify(canvas); // '{"objects":[],"background":"red"}' 添加新對(duì)象 也會(huì)改變json數(shù)據(jù) canvas.add(new fabric.Rect({ left: 50, top: 50, height: 20, width: 20, fill: 'green'})); console.log(JSON.stringify(canvas)); '{"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)"}' 再添加一個(gè) canvas.add(new fabric.Circle({ left: 100, top: 100, radius: 50, fill: 'red'})); console.log(JSON.stringify(canvas)); '{"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)"}' toObject可以轉(zhuǎn)化成js object對(duì)象 { "background" : "rgba(0, 0, 0, 0)", "objects" : [ { "angle" : 0, "fill" : "green", "flipX" : false, "flipY" : false, "hasBorders" : true, "hasControls" : true, "hasRotatingPoint" : false, "height" : 20, "left" : 50, "opacity" : 1, "overlayFill" : null, "perPixelTargetFind" : false, "scaleX" : 1, "scaleY" : 1, "selectable" : true, "stroke" : null, "strokeDashArray" : null, "strokeWidth" : 1, "top" : 50, "transparentCorners" : true, "type" : "rect", "width" : 20 } ]} 每個(gè)fabric對(duì)象有toObject方法 這和toJSON 也有關(guān) 可以自定義 var rect = new fabric.Rect(); rect.toObject = function() { return { name: 'trololo' }; }; canvas.add(rect); console.log(JSON.stringify(canvas)); '{"objects":[{"name":"trololo"}],"background":"rgba(0, 0, 0, 0)"}' 當(dāng)然我們可以保留原有的數(shù)據(jù) 新增數(shù)據(jù) var rect = new fabric.Rect();rect.toObject = (function(toObject) { return function() { return fabric.util.object.extend(toObject.call(this), { name: this.name }); }; })(rect.toObject); canvas.add(rect);rect.name = 'trololo'; console.log(JSON.stringify(canvas)); '{"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)"}' toSvg怎么能不支持轉(zhuǎn)成svg呢 canvas.add(new fabric.Rect({ left: 50, top: 50, height: 20, width: 20, fill: 'green'})); console.log(canvas.toSVG()); '<?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 parserfabric.Canvas#loadfromJSON 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)"}'); 通常情況下 svg 會(huì)被序列化 但是可以使用 fabric.Canvas#toDatalessJSON canvas.item(0).sourcePath = '/assets/dragon.svg'; console.log(JSON.stringify(canvas.toDatalessJSON())); {"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)"} Subclassing構(gòu)造類(lèi) var Point = fabric.util.createClass({ initialize: function(x, y) { this.x = x || 0; this.y = y || 0; }, toString: function() { return this.x + '/' + this.y; } }); 繼承類(lèi) var ColoredPoint = fabric.util.createClass(Point, { initialize: function(x, y, color) { this.callSuper('initialize', x, y); this.color = color || '#000'; }, toString: function() { return this.callSuper('toString') + ' (color: ' + this.color + ')'; } }); 繼承默認(rèn)類(lèi) var LabeledRect = fabric.util.createClass(fabric.Rect, { type: 'labeledRect', initialize: function(options) { options || (options = { }); this.callSuper('initialize', options); this.set('label', options.label || ''); }, toObject: function() { return fabric.util.object.extend(this.callSuper('toObject'), { label: this.get('label') }); }, _render: function(ctx) { this.callSuper('_render', ctx); ctx.font = '20px Helvetica'; ctx.fillStyle = '#333'; ctx.fillText(this.label, -this.width/2, -this.height/2 + 20); } }); 不過(guò)其實(shí)沒(méi)必要的。 該文章在 2023/5/23 16:49:36 編輯過(guò) |
關(guān)鍵字查詢
相關(guān)文章
正在查詢... |