在Fabricjs创建继承类与应用计算变换矩阵

fabricjs提供创建继承类的主要目的是让开发者根据需要在现有类如Rect/Image的基础上自行扩展其父类的功能。矩阵变换在fabricjs中有重要应用,比如点击位置从相对于canvas的坐标到相对于虚拟元素的矩阵变换,再如多个虚拟元素之间绑定变换关系。

创建继承关系类

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中扩展现有类很有帮助。

继承与扩展fabric.Rect类

通常情况,使用Fabricjs在画布上创建一个矩形的方法是使用fabric.Rect构造函数实例化一个矩形对象,然后将矩形对象添加到画布中:

let canvas = new fabric.Canvas('canvas');
canvas.add(new fabric.Rect({
  width: 100,
  height: 100,
  left: 100,
  top: 100,
  rx: 6,
  fill: 'transparent',
  stroke: '#333',
  strokeWidth: 3,
}));

现在有一个需求,在展示上给每一个矩形内部添加文字描述。我可以使用fabric.Text构造函数再实例化一个文本对象,然后将描述矩形含义的文本对象和矩形对象编组。为了减少实例化的对象数量,可以创建一个继承自fabric.Rect的类,用来扩展出可以显示描述的矩形:

let LabelRect = fabric.util.createClass(fabric.Rect, {
  initialize: function(opt) {
    this.callSuper('initialize', opt)
  },
  _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是我自定义的继承fabric.Rect的prototype的构造函数,重写initialize()方法和_render()方法,使用callSuper()调用父类的方法。最后将LabelRect添加到Canvas画布:

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

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

变换矩阵

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函数中的参数非常讲究顺序,所以在调用该函数时要特别注意传入参数的先后次序。

授权账号 » 
原创声明 » 未经授权,请勿复制转载,谢谢配合
联系方式 » 
微信:huazi19930927
邮箱:lanserdi@163.com
发布日期 » 2019年9月5日 周四
Github账号登录以留言