fabric是什么?
它是一个封装了canvas api的库,Canvas提供一个好的画布能力, 但是Api不够友好。绘制简单图形其实还可以, 不过做一些复杂的图形绘制,就没那么方便了,fabric可以解决这个问题,它在原生方法之上提供了一个强大的对象模型。
fabric的简单应用对比
简单应用对比:绘制一个简单的矩形
canvas的实现方式:
drawRect () {
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
ctx.fillStyle = 'red'
// 创建一个坐标100,100,尺寸是20,20的矩形
ctx.fillRect(100, 100, 20, 20)
}
fabric的实现方式
drawRect () {
// 用原生canvas元素创建一个fabric实例
const canvas = new fabric.Canvas('canvas')
// 创建一个矩形对象
const rect = new fabric.Rect({
left: 100,
top: 100,
fill: 'red',
width: 20,
height: 20
})
// 将矩形添加到canvas画布上
canvas.add(rect)
}
区别:
- 在原生的API中,我们是直接对canvas生成的context进行操作的
- 在Fabric中,我们操作对象,实例化它们,更改其属性,并将其添加到画布
更进一步:如果想要矩形增加一定弧度、并且可以被拖动
//fabric
const canvas = new fabric.Canvas('canvas')
const rect = new fabric.Rect({
...
angle: 45,
evented: true,
hasControls: false, // 选中时没有四边操作位
})
canvas.add(rect)
//canvas
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
...
ctx.translate(100, 100);
ctx.rotate(Math.PI / 180 * 45); // 弧度的转化
ctx.fillRect(-10, -10, 20, 20);
canvas.addEventListener('mousemove', (e)=>{
//移动的相关处理
})
区别:fabric的实现方式只需要通过对象属性的简单配置即可实现,原生api需要进行弧度转发、手动实现事件监听的一些处理逻辑,fabric提供了相对完整的事件系统。
fabric还封装了一些其它的基本图形类供直接调用
- fabric.Circle //圆形
- fabric.Ellipse //椭圆
- fabric.Line //线
- fabric.Polygon //多边形
- fabric.Polyline //交叉线
- fabric.Rect //矩形
- fabric.Triangle//三角形
fabric原理
整体结构
渲染逻辑
创建画布主要做了下面几件事情:
const canvas = new fabric.Canvas("canvasId", options);
- 创建缓存canvas:在canvas官方文档中提到的canvas优化方面也包括了在离屏canvas上预渲染相似图形,fabric中的缓存也是一样的,可以通过设置objectCaching是否缓存,如果图形比较复杂的话,建议使用缓存的方法。下面是在复杂图形中两种方式的对比,可以明显感觉到在使用缓存时,fps值大一些。 下图是没有使用缓存的效果图,fps值要小一些,每秒的动画帧要小
使用缓存的效果图如下图,明显动画帧要大一些
使用缓存和不适用缓存的区别: 使用缓存:在渲染之前先用一个离屏 canvas 来做预渲染,在渲染的时候通过调用drawImage把离屏canvas画到真实画布中。不使用缓存: 在渲染的时候直接绘制图形。
- 构建两层canvas元素 当我们执行new fabric.Canvas('canvasId')的时候,页面的dom元素就发生了改变,我们可以通过调试工具发现有两层canvas元素:
fabric这样设计的原因是将渲染层和交互层做分离:lower-canvas 只负责渲染元素;所有的交互,比如框选,事件处理都在 upper-canvas 上。如果我们不需要任何交互,可以使用静态画布:new fabric.StaticCanvas('canvasId', options),这样dom结构就只有一个canvas,没有upper-canvas.
3:事件的绑定等其他处理。
多个图层一起使用的demo(背景图缩放、点拖拽)‘
- canvas对象的实例化及背景图的设置
createCanvas() {
this.canvas = new fabric.Canvas("canvas", {
width: this.containerWidth,
height: 500,
});
this.getImgSize();
this.canvas.setBackgroundImage(
floor,
this.canvas.renderAll.bind(this.canvas),
{
scaleX: this.canvas.width / this.imgWidth, // 背景图片自适应处理
scaleY: this.canvas.height / this.imgHeight,
}
);
}
- 画布的移动处理逻辑:需要使用fabric的relativePan()函数来对画布的位置进行修正,通过官方文档可以知道它接收一个fabric.Point类型的参数。
handleMouseMove(e) {
if (this.moving && e && e.e) {
// 获取当前画布中被选中的图层
if (!this.canvas.getActiveObject()) {
const delta = new fabric.Point(e.e.movementX, e.e.movementY);
this.canvas.relativePan(delta);
}
}
},
- 画布的缩放处理:主要缩放的位置和缩放的倍数处理,同时在官方实例中也能找到以鼠标为中心点进行缩放的示例
handleMouseWheel(e) {
let zoom =
e.e.deltaY > 0
? -0.1 + this.canvas.getZoom()
: 0.1 + this.canvas.getZoom();
zoom = Math.max(0.1, zoom); //最小为原来的1/10
zoom = Math.min(3, zoom); //最大是原来的3倍
const zoomPoint = new fabric.Point(e.pointer.x, e.pointer.y);
this.canvas.zoomToPoint(zoomPoint, zoom);
}
- 文本图层的添加
pointArray: [
{ test: "AAA1", x: 550, y: 50 },
{ test: "BBB1", x: 650, y: 450 },
{ test: "CCC1", x: 400, y: 300 },
{ test: "DDD1", x: 250, y: 50 },
{ test: "EEE", x: 1050, y: 450 },
{ test: "FFF", x: 550, y: 250 },
{ test: "HHH", x: 650, y: 350 },
]
addPoint() {
this.pointArray.forEach((item) => {
const textPoint = new fabric.Text(item.test, {
left: item.x,
top: item.y,
fontSize: 30,
fontWeight: 600,
hasControls: false,
hasBorders: false,
});
// this.initPointEvent(textPoint)
this.canvas.add(textPoint);
});
},