管理杂谈OA答疑ERP答疑教程搜索

Fabric.js 简单介绍和使用


简介

Fabric.js是一个可以简化canvas程序编写的库。 Fabric.js为canvas提供所缺少的对象模型, svg parser, 交互和一整套其他不可或缺的工具。基于MIT协议开源,在github上有许多人贡献代码。

Why fabric?

canvas提供一个好的画布能力, 但其api超级烂。如果你就想画个简单图形, 其实也可以, 不过做一些复杂的图形绘制, 编写一些复杂的效果,就不是那么好了。
fabric就是为此而开发。

用对象的方式去编写代码

举个例子
传统的画正方形代码

// 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);

好的 其实并没有什么差别 不过我们试着旋转一下角度

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);


如果我们想重新调整位置 怎么办

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

  1. fabric.Circle

  2. fabric.Ellipse

  3. fabric.Line

  4. fabric.Polygon

  5. fabric.Polyline

  6. fabric.Rect

  7. fabric.Triangle

画一个三角形 和一个 圆形

// 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

可以简单的使用set来控制对象属性
positioning — left, top;
dimension — width, height;
rendering — fill, opacity, stroke, strokeWidth;
scaling and rotation — scaleX, scaleY, angle;
and even those related to flipping — flipX, flipY.

rect.set('fill', 'red');

rect.set({ strokeWidth: 5, stroke: 'rgba(100,200,200,0.5)' });

rect.set('angle', 15).set('flipY', true);

有了set 其实也就有了get,对象可以创建时设置属性,也可以先实例化再赋值。

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是函数,大家可以使用class继承。

默认值

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 Inheritance

fabric.Object 是图像基类

你可以自己扩充方法

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


Canvas

fabric.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


经典的设计 有options 有对象方法

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你可以轻松的加载一个图片
html

<canvas id="c"></canvas>

<img src="my_image.png" id="my-image">


js

var canvas = new fabric.Canvas('c');

var imgElement = document.getElementById('my-image');

var imgInstance = new fabric.Image(imgElement, {

  left: 100,

  top: 100,

  angle: 30,

  opacity: 0.85

});

canvas.add(imgInstance);

当然也可以通过url加载一张图片到canvas

fabric.Image.fromURL('my_image.png', function(oImg) {

  canvas.add(oImg);

});


可以对加载的图片进行预处理

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

我们已经看了简单的形状,然后图像。更复杂、丰富的形状和内容呢?
路径包括一系列的命令,这基本上模仿一个笔从一个点到另一个。在“移动”,“线”,“曲线”,或“弧”等命令的帮助下,路径可以形成令人难以置信的复杂形状。同组的路径(路径组的帮助),开放更多的可能性。
类似于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);

“M” 代表 “move” 命令, 告诉笔到 0, 0 点.
“L” 代表 “line” 画一条0, 0 到 200, 100 的线.
another “L” creates a line to 170, 200.
z” tells forces drawing pen to close current path and finalize the shape.

...

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也可以设置canvas属性

当然,太困然了,所以你可以使用 fabric.loadSVGfromString or fabric.loadSVGfromURL 方法。

Afterword

看些demo吧。


上面我们学习了基础用法,现在我们开始一些好玩的。

Animation

我们先回顾设置一下正方形角度方法

rect.set('angle', 45);

这是没有动画的

Fabric object都有animate方法

rect.animate('angle', 45, {
  onChange: canvas.renderAll.bind(canvas)
});

那么正方形会从0到45有一个动画过度

从左到右进行变动

rect.animate('left', '+=100', { onChange: canvas.renderAll.bind(canvas) });

逆时针转5度

rect.animate('angle', '-=5', { onChange: canvas.renderAll.bind(canvas) });

当然animate还支持这些方法

  1. from: Allows to specify starting value of animatable property (if we don't want 2. current value to be used).

  2. duration: Defaults to 500 (ms). Can be used to change duration of an animation.

  3. onComplete: Callback that's invoked at the end of the animation.

  4. easing: Easing function.

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一次可以使用多个效果

当然 你也可以自己定义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);
};

Colors

fabric 支持 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)');

并且支持相互转换

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'
  }
});

首先确定两个点 在其距离中以百分比定位颜色

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

  1. Multiline support. Native text methods unfortunately simply ignore new lines.

  2. Text alignment. Left, center, right. Useful when working with multiline text.

  3. Text background. Background also respects text alignment.

  4. Text decoration. Underline, overline, strike-through.

  5. Line height. Useful when working with multiline 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

怎么可以没有事件呢

事件以on off使用 canvas 可以捕捉事件

mouseevent
"mouse:down", "mouse:move", and "mouse:up".

renderevent
"after:render".

selectionevent
"before:selection:cleared", "selection:created", "selection:cleared".

objectevent
object ones: "object:modified", "object:selected", "object:moving", "object:scaling", "object:rotating", "object:added", and "object:removed"

var canvas = new fabric.Canvas('...');
canvas.on('mouse:down', function(options) {
    console.log(options.e.clientX, options.e.clientY);
});

同样这些事件也可以用任何fabric对象监听

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');
});


上面我们学习了基础和高级特性,现在介绍更神奇的东西。

Groups

话说这个功能我最喜欢,组成群组可以统一修改其中所有组件属性,如何定义:

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);

图片描述

现在我们就可以对其中的对象集修改

group.item(0).setFill('red');
group.item(1).set({
  text: 'trololo',
  fill: 'white'
});

图片描述

group中的元素相对于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 可以动态添加

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'
}));

当然 你可以使用canvas上已有的进行克隆 组合

// 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, toJSON

canvas 实现了toJSON接口 可以被序列化

var canvas = new fabric.Canvas('c');
JSON.stringify(canvas); // '{"objects":[],"background":"rgba(0, 0, 0, 0)"}'

canvas 可以随时被修改 json数据会被修改

canvas.backgroundColor = 'red';
JSON.stringify(canvas); // '{"objects":[],"background":"red"}'

添加新对象 也会改变json数据

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)"}'

再添加一个

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

可以转化成js object对象

{ "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
    }
  ]}

每个fabric对象有toObject方法 这和toJSON 也有关 可以自定义

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)"}'

当然我们可以保留原有的数据 新增数据

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

怎么能不支持转成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 parser

fabric.Canvas#loadfromJSON
fabric.Canvas#loadfromDatalessJSON
fabric.loadSVGfromURL
fabric.loadSVGfromString

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 会被序列化 但是可以使用 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

构造类

var Point = fabric.util.createClass({
  initialize: function(x, y) {    this.x = x || 0;    this.y = y || 0;
  },
  toString: function() {    return this.x + '/' + this.y;
  }
});

继承类

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 + ')';
  }
});

继承默认类

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);
  }
});

不过其实没必要的。


更多精彩文章浏览...
点击右上角图标分享到朋友圈
官方网站:http://www.clicksun.cn
咨询热线:400-186-1886
服务邮箱:service@clicksun.cn