前言
最近在使用 Fabric.js 开发图形操作项目,用过 Fabric.js 都知道他是属于面向对象式的操作图形,也内置了非常多的对象类型,但是总有不合适的时候,比如你想要的对象他是一个很特殊的对象,那么这个时候,就需要用到 自定义对象 这个功能了,可以用来定义我们自己的对象。
本文将以自定义箭头对象为例,详细介绍如何在 Fabric.js 中创建和使用自定义对象。
什么是自定义对象
在Fabric.js中,自定义对象是指基于现有基本对象(如Line、Path、Group等)扩展出来的新类型对象,它继承了基本对象的属性和方法,同时添加了自己特有的功能和行为。自定义对象使我们能够:
- 创建更复杂、更专业的图形元素
- 封装特定的业务逻辑和交互行为
- 提高代码的复用性和可维护性
- 实现统一的样式和行为管理
例如,Fabric.js没有内置的箭头对象,但我们可以基于Line对象自定义一个Arrow对象,使其在线条的一端或两端绘制箭头。
要如何自定义对象
在Fabric.js中自定义对象其实非常简单,我们都知道 Fabric.js 其实有一个所有对象都共同的基类也就是 Object。那些提供给我们使用的基本对象比如 Line,Group,Path 都是继承于这个基类,所以我们需要自定义对象的话,就也需要使用同样的方法,通过继承来实现。
选择合适的基类
那么首先我们就需要选择合适的基类,根据你要创建的对象特性,选择最接近的Fabric.js基本对象作为父类。
比如我想要绘制箭头,这个对象的行为与 Line 非常相似,只不过需要在头部或者尾部添加箭头的标志,那么我们就可以选择 Line 来作为基类
createClass创建子类
然后使用createClass方法创建子类,Fabric.js 提供了 util.createClass 方法来创建新的类。
fabric.Arrow = fabric.util.createClass(fabric.Line, {}});
重写基类逻辑
然后我们可以选择性的重写基类中的某些方法,以此来实现自己想要的自定义逻辑:
-
重写 initialize 方法:定义对象的初始化逻辑,设置默认属性。这个方法通常包含两个参数:
-
第一个参数:根据对象类型不同而不同,对于Arrow这类继承自Line的对象,是表示线段端点的坐标数组 [x1, y1, x2, y2]
-
第二个参数:options 对象,包含各种配置属性(如颜色、线宽、填充等)
-
-
重写 _render 方法:自定义对象的渲染逻辑。这个方法就是定义在每一次重渲染之后,这个对象要绘制成什么模样。方法存在一个入参 ctx:
- ctx: Canvas 2D渲染上下文对象,提供各种绘图API
- 我们可以通过在内部调用 this.callSuper('_render', ctx); 来绘制继承的父类的基本形状,比如继承了line,那么这段代码就会绘制出line的基本图案,然后我们在在这之上绘制自己需要的部分,这方面的绘制就是用到canvas的原生知识。
-
实现 fromObject 方法:允许从JSON对象创建实例,支持序列化和反序列化。我们一般在 Fabric.js 上绘图之后会将其内容导出保存,或者将这份内容导入重绘,这些都是需要数据作为支持,那么这个方法就是让我们可以导出自定义的那部分属性,保证在导出和重绘的时候不会出现异常。
注册自定义类
在实现了自定义对象后,我们就需要将他注册到 Fabric.js 里面,这样我们就可以直接通过 fabric.xxx 的方式调用这个对象。
fabric.Arrow = fabric.util.createClass(fabric.Line, { xxx });
实现一个自定义对象
下面,我们将实现一个自定义的Arrow(箭头)对象,它基于Fabric.js的Line对象,但在线条的一端添加了箭头。
基本实现
// 创建Arrow类,继承自Line
fabric.Arrow = fabric.util.createClass(fabric.Line, {
// 定义类型名称
type: 'arrow',
superType: 'drawing',
// 初始化方法
initialize(points, options) {
if (!points) {
const { x1, x2, y1, y2 } = options;
points = [x1, y1, x2, y2];
}
options = options || {};
this.callSuper('initialize', points, options);
},
// 自定义渲染方法
_render(ctx) {
// 首先渲染线条部分(调用父类的渲染方法)
this.callSuper('_render', ctx);
// 保存当前上下文状态
ctx.save();
// 抵消元素放缩造成的影响,使箭头不会变形
ctx.scale(1 / this.scaleX, 1 / this.scaleY);
// 计算线条的角度
const xDiff = (this.x2 - this.x1) * this.scaleX;
const yDiff = (this.y2 - this.y1) * this.scaleY;
const angle = Math.atan2(yDiff, xDiff);
// 将坐标系移动到线条中点
ctx.translate(((this.x2 - this.x1) / 2) * this.scaleX, ((this.y2 - this.y1) / 2) * this.scaleY);
// 旋转坐标系,使X轴与线条方向一致
ctx.rotate(angle);
// 绘制箭头
ctx.beginPath();
// 从线条前方5px开始绘制,避免与线条端点重叠
ctx.moveTo(5, 0);
ctx.lineTo(-5, 5);
ctx.lineTo(-5, -5);
ctx.closePath();
// 设置样式并填充
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = this.stroke;
ctx.fillStyle = this.fill;
ctx.stroke();
ctx.fill();
// 恢复上下文状态
ctx.restore();
},
});
// 实现fromObject方法,支持序列化和反序列化
fabric.Arrow.fromObject = (options, callback) => {
const { x1, x2, y1, y2 } = options;
return callback(new fabric.Arrow([x1, y1, x2, y2], options));
};
使用自定义Arrow对象
创建自定义Arrow对象后,我们可以像使用其他Fabric.js对象一样使用它:
const arrow = new fabric.Arrow([50, 50, 200, 200], {
stroke: 'red',
strokeWidth: 3,
fill: 'red'
});
canvas.add(arrow);
arrow.set({
stroke: 'blue',
strokeWidth: 5
});
canvas.renderAll();
5. 总结
通过本文的介绍和实例,我们了解了如何在Fabric.js中创建自定义对象。自定义对象是扩展Fabric.js功能的强大方式,它允许我们创建更复杂、更专业的图形元素,满足特定的业务需求。