当前位置: 首页 > 工具软件 > Fabric.js > 使用案例 >

fabric.js 中文教程第六部分

武晨
2023-12-01

了解转换如何在fabricJS上工作是尽可能顺利地编写应用程序的关键方面。

与转换相关的方法和属性

如果您计划理解和使用与自定义代码一起使用的fabricJS转换,那么这些方法应该是您最应该学习使用的方法。
一般来说,在本页面中,我们将矩阵称为6个数字的数组,表示平面上的转换,并将其作为一个类似{x: number, y: number}简单的JS对象或者是fabric.Point类的实例。(通常没有区别)

Canvas:
- viewportTransform = 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);

从一个空间移动到另一个空间(空间变换)

使用fabricJS,您通常需要与坐标和位置交互,但如果您没有正确的背景,了解这些坐标的位置可能会很麻烦。
我将列出转换及其用法,然后我将尝试举两个例子来更好地阐明发生了什么,以及如何实现。

Canvas.viewportTransform:将虚拟画布的一点移动到缩放和平移空间。
当画布未缩放和未平移时,在应用从视图端口Transfrom M表示的缩放和平移后,位于位置P的点可以在坐标处找到:

newP = fabric.util.transformPoint(P, canvas.viewportTransform);

Object.calcTransformMatrix:返回表示特定时刻的通用对象变换的矩阵(受顶部、左侧、缩放和许多其他属性的影响),并将点从对象空间移动到画布空间,而不是缩放。因此,给定对象空间坐标中位于坐标P处的点,该点将在画布上的以下位置绘制:

newP = fabric.util.transformPoint(P, object.calcTransformMatrix());

变形顺序

在渲染期间,Fabric按以下顺序应用变换:

zoom and pan => object transformation => nested object ( group ) => additionally nested objects ( nested groups )

恢复顺序

invertTransform实用程序用于在转换逻辑中返回,以便进行一些反向计算:
想象一下,你想在画布上标记一个对象,在点击的点上单击鼠标。在点P处单击,例如元素上的像素为10,10。对象被缩放和旋转,画布被缩放和平移。
要反转渲染计算,可以遵循以下逻辑:

// calculate the total transformation that is applied to the objects pixels:
var mCanvas = canvas.viewportTransform;
var mObject = object.calcTransformMatrix();
var mTotal = fabric.util.multiplyTransformMatrices(mCanvas, mObject); // inverting the order gives wrong result
var mInverse = fabric.util.invertTransform(mTotal);
var pointInObjectPixels = fabric.util.transformPoint(pointClicked, mInverse);

现在,pointInObjectPixels是一个位于坐标空间中的点,其中0,0位于对象的中心。

了解矩阵的效果

给定top,left,angle,scaleX,scaleY,skewX,skewY,flipX,flipY,创建表示该变换的矩阵相对简单。
如何恢复不是那么直观。一个矩阵有6个维度,是6个数字,而属性是7,因为我们可以将翻转同化为比例。确实有无限多的矩阵,但可能的属性组合的数量要大无穷多。

fabric.util.qrDecompose(matrix)来了,可以为我们解码矩阵。给定函数的一般可逆矩阵,它返回一个包含这些信息的选项对象:

{
  angle: number, // in degree
  scaleX: number,
  scaleY: number,
  skewX: number, // in degree
  skewY: 0, // always 0! in degree.
  translateX: number,
  translateY: number,
}

该函数给出了该矩阵的一个可能解,将skewY约束为0。

一个真实的用例

一位开发人员希望将对象分组在一起,但同时保持它们的自由。理想情况下,当一个主对象移动时,他希望其他一些对象跟随它。
为了解释这个例子,我将调用主对象BOSS和其他MINIONS。
让我们想象一下,画布周围有一些物体,我们可以自由移动它们。在某个时刻,我们想锁定它们的相对位置,并一起缩放并移动一个。当我们设置我们想要的位置时,BOSS的位置由一个矩阵来描述,正如我们现在所了解的,每个MINIONS也是如此。
我确信它存在着一个矩阵,它定义了从boss到minion的必要转变,我必须找到它。

// i m looking for the UNKNOW relation matrix where:
BOSS * UNKNOW = MINION
// i multiply left for BOSS-INV
BOSS-INV * BOSS * UNKNOW = BOSS-INV * MINION
// BOSS-INV * BOSS = IDENTIY, a neutral matrix.
IDENTITY * UNKNOW = BOSS-INV * MINION
// so...
UNKNOW = BOSS-INV * MINION
// that in fabricJS code equals to:
var minions = canvas.getObjects().filter(o => o !== boss);
var bossTransform = boss.calcTransformMatrix();
var invertedBossTransform = fabric.util.invertTransform(bossTransform);
minions.forEach(o => {
  var desiredTransform = multiply(invertedBossTransform, o.calcTransformMatrix());
  // save the desired relation here.
  o.relationship = desiredTransform;
});

好了,现在我知道了如何找到关系,我可以编写一些事件处理程序来将这种关系应用到每个BOSS操作中。

var canvas = new fabric.Canvas('c');
var boss = new fabric.Rect(
  { width: 150, height: 200, fill: 'red' });
var minion1 = new fabric.Rect(
  { width: 40, height: 40, fill: 'blue' });
var minion2 = new fabric.Rect(
  { width: 40, height: 40, fill: 'blue' });

canvas.add(boss, minion1, minion2);

boss.on('moving', updateMinions);
boss.on('rotating', updateMinions);
boss.on('scaling', updateMinions);

var multiply = fabric.util.multiplyTransformMatrices;
var invert = fabric.util.invertTransform;

function updateMinions() {
  var minions = canvas.getObjects().filter(o => o !== boss);
  minions.forEach(o => {
    if (!o.relationship) {
      return;
    }
    var relationship = o.relationship;
    var newTransform = multiply(
      boss.calcTransformMatrix(),
      relationship
    );
    opt = fabric.util.qrDecompose(newTransform);
    o.set({
      flipX: false,
      flipY: false,
    });
    o.setPositionByOrigin(
      { x: opt.translateX, y: opt.translateY },
      'center',
      'center'
    );
    o.set(opt);
    o.setCoords();
  });
}

document.getElementById('bind').onclick = function() {
  var minions = canvas.getObjects().filter(o => o !== boss);
  var bossTransform = boss.calcTransformMatrix();
  var invertedBossTransform = invert(bossTransform);
  minions.forEach(o => {
    var desiredTransform = multiply(
      invertedBossTransform,
      o.calcTransformMatrix()
    );
    // save the desired relation here.
    o.relationship = desiredTransform;
  });
}

查看demo

 类似资料: