创作者 » 陈帅华
版权声明 » 自由转载-保持署名-非商用-非衍生
发布日期 » 2019年9月5日 周四

Fabric.js扩展自定义类

全篇共 3890 字。按500字/分钟阅读,预计用时 7.8 分钟。总访问 526 次,日访问 2 次。

Fabric.js在 util 命名空间中提供了各种有用的工具函数,其中就包括创建类的函数 fabric.util.createClass()。使用该函数创建的类,能继承开发者指定的父类的特性,并且还能重写父类的方法以及为新创建的类定义新的方法。比如你可以创建继承了 Fabric 中既有的矩形的新类,并且你可以让新创建类在画布上渲染时,在绘制矩形的基础上再绘制一段文本。

Fabric 官方文档中是这样描述该方法的,它存在于 fabric.util 命名空间内。如果把 util 看作是类的话,那么 createClass 就是 util 的静态类方法:

(static) createClass(parentopt, propertiesopt)
参数名称 参数类型 是否必填 描述
parentopt function 可选 所要继承的类
propertiesopt object 可选 当前类的实例所共享的属性

首先,我不传入任何参数,观察控制台里输出的执行 createClass 方法后返回的对象:

const Klass_01 = fabric.util.createClass();
console.dir(Klass_01);

控制台输出fabric.util.createClass()执行后返回的对象

控制台输出了一个可调用对象——函数。观察该函数,发现其中包含 prototype 属性,并且满足条件,可知 createClass 方法返回的是构造函数:

Klass_01.prototype.constructor === Klass_01 // true

下面使用 createClass 方法扩展 fabric.React 类。想要达到的目的是,新的类构造出来的实例所绘制的矩形内部有文字描述:

let LabelRect = fabric.util.createClass(fabric.Rect, {
  _render(ctx) {
    this.callSuper('_render', ctx);
    ctx.font = '20px Helvetica';
    ctx.fillStyle = '#333';
    ctx.fillText(this.get('label'), -this.width / 2 + 5, -this.height / 2 + 20);
  }
});

LabelRect 也拥有 superClasss 和 subClasses 两个静态类属性,前者指向当前类的父类,后者包含所有的子类,现在 fabric.Rect 的 subClasses 中就包含了LabelRect 这一构造函数。

阅读源代码,fabric.Object 拥有私有的 _render 方法,fabric.Rect 继承自 fabric.Object。自定义的 LabelRect 继承自 fabric.Rect,重写 _render 方法。

/**
 * @private
 * function that actually render something on the context.
 * empty here to allow Obects to work on tests to benchmark fabric functionalites
 * not related to rendering
 * @param {CanvasRenderingContext2D} ctx Context to render on
 */
_render: function(/* ctx */) {
},

LabelRect是我自定义的继承fabric.Rect的prototype的构造函数,重写initialize()方法和_render()方法,使用callSuper()调用父类的方法。最后将LabelRect添加到Canvas画布:

canvas.add(new LabelRect({
  width: 100,
  height: 100,
  left: 100,
  top: 100,
  rx: 6,
  label: '陈帅华',
  fill: 'transparent',
  stroke: '#333',
  strokeWidth: 3,
}));

自定义继承自fabric.Rect的带描述的矩形对象

D家族的维度进化史

Fabricjs在fabric.util命名空间内添加了一个名为createClass的工具函数,用于创建类。它的初衷是继承Fabricjs中现有类并创建子类,但我也可以用这个Fabricjs提供的工具函数创建我理解意义上的包含原型和原型链知识的类。为了便于我理解Fabricjs中对类的定义,我想出了一个有关从0维生物上升到4维的类继承关系。首先定义一个非常简单的0维生物类,我暂且称呼他们D类,其中一个属于D类的生物,我暂且称呼它小d,D类中有不计其数的和小d一样的生物。小d很简单,简单到没有空间、没有时间,D类大家庭都不知道自己从哪里来要到哪里去:

fabric.util.createClass(null, {});

0维生物与fabricjs中的继承类

小d继承了D0类的callSuper()initialize()两个行为。callSuper行为是对D0这个类的父类永恒的呼喊,这是从D0家族出现以来就具备的本能——烙印在D0类家族基因里的对造物主的质问,过去的过去是什么。initialize保证D0家族的繁衍与生生不息。后来D类生物不知何故从0维上升到1维空间,突变为D1类生物,小d也不例外,但他们还保留着D0家族无法摆脱掉的质问过去的本能:

let D1 = fabric.util.createClass(D0, {
  initialize: function(x) {
    this.callSuper('initialize');
    this.setX(x || 3);
  },
  setX: function(x) {
    this.x = x;
  }
});

1维生物与fabricjs中的继承类

随着D0生物类突变为D1生物类,小d竟然具备了运动的能力,虽然只是在单个方向的移动,而且它有两个最要好的朋友在其左右,好像D1家族里的每个人都有且只有两个最好的朋友。然而自然法则就是这样把,在维持稳定的同时打破这种稳定建立新的稳定。D1生物突变到D2:

let D2 = fabric.util.createClass(D1, {
  initialize: function(x, y) {
    this.callSuper('initialize', x);
    this.setY(y || 6);
  },
  setY: function(y) {
    this.y = y;
  }
});

2维生物与fabricjs中的继承类

又过了很久很久…。到今天,后代的小d们已经升到四维空间:

let D4 = fabric.util.createClass(D3, {
  initialize: function(x, y, z, t) {
    this.callSuper('initialize', x, y, z);
    this.setT(z || 12);
  },
  setT: function(t) {
    this.t = t;
  }
});

4维生物与fabricjs中的继承类

跳出小d的故事情景,回到代码中。从上图中的原型链继承关系可以清楚的看到,变量d是D4类的实例,所以d自然可以调用D4类中声明的setT()方法;而D4类又继承自D3类,所以d还可以调用D3中声明的setZ()方法;而D3类又继承自D2类,所以变量d还可以调用D2类中声明的setY()方法;D2类又继承自D1,d可以调用D2类中声明的setX()方法;D1类继承自D0类,D0类继承Object

由于fabricjs给每一个构造函数的prototype属性都添加callSuper()initialize()方法,所以每当实例调用callSuper()方法时,调用的是构造自己的构造函数的prototype对象的callSuper()方法,而不是原型链上更远的callSuper()方法。这是因为实例会从自己自由属性上找,找不到就从最近的原型开始找,直到从原型链上找到为止。基于这一原理,子类可以覆盖祖先类的属性和方法,这对下面理解在fabricjs中扩展现有类很有帮助。

变换矩阵

Fabricjs中设计变换矩阵的属性和方法如下:

Canvas:
- vieportTransform = matrix;
Objects:
- matrix = fabric.Object.prototype.calcTransformMatrix();
- matrix = fabric.Object.prototype.calcOwnMatrix();
Utils:
- point = fabric.util.transformPoint(point, matrix);
- matrix = fabric.util.multiplyTransformMatrices(matrix, matrix);
- matrix = fabric.util.invertTransform(matrix);
- options = fabric.util.qrDecompose(matrix);

传入fabric.util.multiplyTransformMatrices函数中的参数非常讲究顺序,所以在调用该函数时要特别注意传入参数的先后次序。


Fabric.js官方网站

:)记录下你此刻的想法~
来自笔友的留言
JavaScript核心
微信公众平台开发
翻译计划
Node.js实战
数据可视化
UI与动画
网站运维
生活趣味
帅华君的书单
效率脚手架
CSS样式经验之谈
硬件编程