Fabric.js 是一个强大且简单的Javascript HTML5 Canvas库。
官网地址:http://fabricjs.com/
Canvas提供一个好的画布能力, 但是Api不够友好。绘制简单图形其实还可以, 不过做一些复杂的图形绘制, 编写一些复杂的效果,就不是那么方便了。Fabric.js就是为此而开发,它主要用对象的方式去编写代码。
在Canvas上创建、填充图形(包括图片、文字、规则图形和复杂路径组成图形)。
给图形填充渐变颜色。
组合图形(包括组合图形、图形文字、图片等)。
设置图形动画及用户交互。
生成JSON、SVG数据等。
生成Canvas对象自带拖拉拽功能。
它提供了灵活丰富的Api和可配置化参数轻松实现复杂的效果,该开源库已被许多开发者用于项目实践中,广受好评。
下载趋势图
这里基于React框架为基础,介绍Fabric.js开发实战实例。
npm install fabric --save
或
yarn add fabric
import {fabric} from 'fabric';
//创建画布
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;
//元素点击选中事件处理
canvasObj.on('selection:created', function(options) {
//console.log('selection:created');
//console.log(options);
if (options.target) {
// TODO
}
}
//元素选中更新事件处理
canvasObj.on('selection:updated', function(options) {
//console.log('selection:updated');
//console.log(options);
if (options.target) {
// TODO
}
}
//元素取消选中事件处理
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) {
// TODO
}
}
//对象缩放完成事件处理
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) {
}
}
/**
* 拖拽事件处理 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
*/
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();
});
//加载字体库数据, 默认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);
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'});
}
将两个元素深深地绑定在一起,任何操作同时对两个有效,这里叫组的概念
const circle = new fabric.Circle({
radius: 100,
fill: '#eef',
scaleY: 0.5,
originX: 'center',
originY: 'center'
});
const text = new fabric.Text('hello world', {
fontSize: 30,
originX: 'center',
originY: 'center'
});
const group = new fabric.Group([ circle, text ], {
left: 150,
top: 100,
angle: -10
});
怎么理解就是将一个元素变为另一个元素的boss,boss元素拖动旋转操作,另一个元素跟着同样变,但是反过来就不行
bindMinionToBoss(canvas: fabric.Canvas, boss: fabric.Group): void {
const minions = canvas.getObjects().filter((o: any) => o !== boss);
const bossTransform = boss.calcTransformMatrix();
const invertedBossTransform = fabric.util.invertTransform(bossTransform);
minions.forEach((o: any) => {
const desiredTransform = fabric.util.multiplyTransformMatrices(
invertedBossTransform,
o.calcTransformMatrix()
);
o.relationship = desiredTransform;
});
boss.on("moving", () => {
minions.forEach((o: any) => {
if (!o.relationship) return;
const newTransform = fabric.util.multiplyTransformMatrices(
boss.calcTransformMatrix(),
o.relationship
);
const opt = fabric.util.qrDecompose(newTransform);
const point = new fabric.Point(opt.translateX, opt.translateY);
o.setPositionByOrigin(point, "center", "center");
o.set(opt);
o.setCoords();
});
});
}
其他详细Api请参见:
http://fabricjs.com/docs/
另外还有很多好玩有趣的功能等着我们去发现,可以参照官网的例子:
http://fabricjs.com/demos/
如果使用中出现常见问题,优先参考:
http://fabricjs.com/fabric-gotchas