Fabric.js框架开发实践

StoneLee 发布于 文章字数: 1.1k 阅读次数:

什么是Fabric.js?

Fabric.js 是一个强大且简单的Javascript HTML5 Canvas库。

为什么要使用Fabric.js?

Canvas提供一个好的画布能力, 但是Api不够友好。绘制简单图形其实还可以, 不过做一些复杂的图形绘制, 编写一些复杂的效果,就不是那么方便了。Fabric.js就是为此而开发,它主要用对象的方式去编写代码。

Fabric.js能做的事情

  • 在Canvas上创建、填充图形(包括图片、文字、规则图形和复杂路径组成图形)。
  • 给图形填充渐变颜色。
  • 组合图形(包括组合图形、图形文字、图片等)。
  • 设置图形动画集用户交互。
  • 生成JSON、SVG数据等。
  • 生成Canvas对象自带拖拉拽功能。

项目开发实战

这里基于React框架为基础,介绍Fabric.js开发实战实例。

1、安装Fabric.js
1
2
3
npm install fabric --save

yarn add fabric
2、引入Fabric.js
1
import {fabric} from 'fabric';
3、initCanvas 画布初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//创建画布
let canvasObj = new fabric.Canvas('snackCanvas');
//设置画布背景色
canvasObj.setBackgroundColor('#d5d5d5');
//设置画布宽度
canvasObj.setWidth(this.state.canvasWidth);
//设置画布高度
canvasObj.setHeight(this.state.canvasHeight);
//标识画布中元素选中时,是否还按原有的层级位置展示
canvasObj.preserveObjectStacking = true;

/**
* 设置元素选中框的样式
*/

//边角节点背景透明 false
fabric.Object.prototype.transparentCorners = false;
//边角节点大小
fabric.Object.prototype.cornerSize = 6;
//边框颜色
fabric.Object.prototype.borderColor = 'rgba(83,152,248,1)';
//角节点内部颜色
fabric.Object.prototype.cornerColor = 'white';
//角节点边框颜色
fabric.Object.prototype.cornerStrokeColor = 'rgba(83,152,248,1)';

/**
* 设置对象位置Left/Top的基准参考位置为自身中心点
* 默认 对象采用相对自身中心点旋转,即centeredRotation=true
*/

fabric.Object.prototype.originX = 'center';
fabric.Object.prototype.originY = 'center';

this.editor = canvasObj;
4、画布事件监听
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//元素点击选中事件处理
canvasObj.on('selection:created', function(options) {
//console.log('selection:created');
//console.log(options);

if (options.target) {

}
}

//元素选中更新事件处理
canvasObj.on('selection:updated', function(options) {
//console.log('selection:updated');
//console.log(options);

if (options.target) {

}
}

//元素取消选中事件处理
canvasObj.on('selection:cleared', function(options) {
//console.log('selection:cleared');

}

//对象移动完毕事件处理
canvasObj.on('object:moved', function(options) {
//console.log('moved');
//console.log(options);

if (options.target) {

}
}

//对象旋转完成事件处理
canvasObj.on('object:rotated', function(options) {
//console.log('rotated');
//console.log(options);

if (options.target) {

}
}

//对象缩放完成事件处理
canvasObj.on('object:scaled', function(options) {
//console.log('scaled');
//console.log(options);

if (options.target) {

}
}

//对文本编辑修改后
canvasObj.on('text:changed', function(options) {
//console.log('text:changed');
//console.log(options);

if (options.target) {

}
}
5、画布拖拽事件处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* 拖拽事件处理 start
*/

document.addEventListener('dragstart', function(e){
//拖拽图片,并传递对象
if(e.target.className == 'img'){
scope.mouseX = e.offsetX;
scope.mouseY = e.offsetY;

//拖拽数据
let sourceStr = e.target.dataset.source;
if(sourceStr){
scope.dragData = JSON.parse(sourceStr);
}
scope.figureType = e.target.className;
}
}, false);

document.addEventListener('dragover', function(e){
e.preventDefault();

}, false);

//拖拽动作
this.dragObj('drop');

dragObj(eventName){
let scope = this;
this.editor.on(eventName, function(opt){
if((opt.e.target.className).trim() == 'upper-canvas' ){
scope.dragEvt(eventName, opt);
}
});
}

dragEvt(eventName, opt){
let scope = this;

if(eventName == 'dragover'){
opt.e.preventDefault();

}else if(eventName == 'drop'){//拖拽结束
opt.e.preventDefault();
//console.log(this.dragData);
//
//对拖拽数据进行业务处理
//
}
}
/**
* 拖拽事件处理 end
*/

6、画布中的图片加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const dragImageUrl = this.dragData.imageUrl;
fabric.Image.fromURL(dragImageUrl, function(image){
image.set({
id: getUUID(),
left: imageLeft,
top: imageTop,
width: nodeWidth,
height: nodeHeight,
classname: 'img',
source: scope.dragData,
selectable,
hasContorls
})
.scale(scope.state.canvasScale, scope.state.canvasScale)
.setCoords();

//添加到画布
scope.editor.add(image);

//设置当前素材为选中状态
scope.editor.setActiveObject(image);

//重新渲染
scope.editor.requestRenderAll();
});
6、画布中的字体库加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//加载字体库数据, 默认load()方法 超时时长默认为3秒钟
loadAndUse(object, fontName, scope) {
let myfont = new FontFaceObserver(fontName);
myfont.load(null, 5000)
.then(function() {
// when font is loaded, use it.
if(object){
object.source.fontFamily = fontName;
object.set("fontFamily", fontName).setCoords();
scope.editor.requestRenderAll();
}
}).catch(function(e) {
console.log(e);
alert('字体 ' + fontName + ' 加载失败。');
});
}

//字体方法的使用
this.loadAndUse(null, '宋体', this);
7、画布内容转换成图片保存到后台
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
saveData(){
...
省略其他代码 ...
let paramData = new FormData();

let dataUrl = this.editor.toDataURL();

let blobImage = this.dataURLtoBlob(dataUrl);

blobImage.contentType = 'application/octet-stream';

paramData.append("file", blobImage);

...
省略其他代码 ...
}

//数据类型转换
dataURLtoBlob(dataurl){
let arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while(n--){
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], {type:'application/octet-stream'});
}