无处不在的前端矩阵变换(下)

简介: 在前端开发中不论CSS3也好,SVG也好,还是Canvas,无论多复杂的图形,都是由一个个点组成的。一个元素渲染后就可以得到一张位图,然后对这个位图上每一点进行变换,就可以得到新的一张位图,从而产生了视觉上的平移translate、旋转rotate、缩放scale、拉伸skew 等效果。这一切都是变换矩阵的功劳。

canvas中的矩阵变换


同样结合一个canvas开源库 fabric.js 来说下canvas中的矩阵变换应用,这个库实现很牛叉,里面有大量的功能点,比如: 通过 canvas 绘制的元素如何解决Retina 屏中元素模糊的问题,以及如何采用离线canvas做性能提升等等。


<!--dom 作为canvas绘制画布-->
<div id="drawing"></div>
<!--js-->
let canvas = new fabric.Canvas('canvas', { width: "375", height: "500" })
let react = new fabric.Rect({ width: 100, height: 100, left: 100, top: 100, fill: 'red', angle: 10 })
canvas.add(react)


如图:



同样说下fabric.js 是如何绘制的


<!-- fabric.js 部分代码截取,有大量的代码省略,只挑取关键流程代码 -->
canvas.add(react)
=>
add: function () {
  /* 变量说明:
    this => new fabric.Canvas 实例对象
  */
  this.renderOnAddRemove && this.requestRenderAll(); 
  return this; 
},
// ...省略部分代码...
renderAll: function () {
  /* 变量说明:
    this => new fabric.Canvas 实例对象
    this.contextContainer => 如上图class="lower-canvas" canvas元素的2d绘图上下文
    this._chooseObjectsToRender() => 获取要渲染的object对象,这里就是react实例对象
  */
  var canvasToDrawOn = this.contextContainer;
  this.renderCanvas(canvasToDrawOn, this._chooseObjectsToRender()); 
  return this;
},
renderCanvas: function(ctx, objects) {
  /* 变量说明:
    this => new fabric.Canvas 实例对象
    ctx => 如上图class="lower-canvas" canvas元素的2d绘图上下文
    objects =>  fabric.js 创建的react实例对象
  */
  // 擦除lower-canvas
  this.clearContext(ctx);  
  ctx.save();
  // 对object元素进行绘制draw
  this._renderObjects(ctx, objects);
  ctx.restore();
},
// ...省略部分代码,最终调用object的render方法,如下 ...
render: function(ctx) {
  /* 变量说明:
    this => fabric.js 创建的react实例对象
    ctx => 如上图class="lower-canvas" canvas元素的2d绘图上下文
  */
  ctx.save();  
  // 对ctx坐标进行变换 很重要 
  this.transform(ctx);
  // 如果元素需要走缓存策略:则会创建缓存canvas, 然后在缓存canvas上绘制object对象(这里是fabric.js 创建的react实例对象),最后再把缓存canvas => ctx.drawImage(this._cacheCanvas, -this.cacheTranslationX, -this.cacheTranslationY); 绘制到lower-canvas上
  if (this.shouldCache()) {
    // 创建了一个缓存canvas、并在缓存canvas上draw绘制object元素
    this.renderCache();
    // 然后将缓存的canvas绘制到lower-canvas中
    this.drawCacheOnCanvas(ctx);
  }
  else {
    this._removeCacheCanvas();
    this.drawObject(ctx);
  }
  ctx.restore();
  // ...省略部分代码...
},
// 接下来主要看 this.transform(ctx); 对lower-canvas的坐标变换
transform: function(ctx) {
  var m;
  if (this.group && !this.group._transformDone) {
    m = this.calcTransformMatrix();
  }
  else {
    // 根据object的信息, 计算矩阵
    m = this.calcOwnMatrix();
  }
  // 将lower-canvas坐标系平移到
  ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
},
calcOwnMatrix: function() {
  // ...省略部分代码...
  /* 变量说明:
    this => fabric.js 创建的react实例对象,也即是object
  */
  var tMatrix = this._calcTranslateMatrix();
  this.translateX = tMatrix[4];
  this.translateY = tMatrix[5];
  cache.key = key;
  cache.value = fabric.util.composeMatrix(this);
  return cache.value;
},
..........  下面这部分是计算object经过scale、rotate等后的中心点坐标 ....................
_calcTranslateMatrix: function() {
  var center = this.getCenterPoint();
  return [1, 0, 0, 1, center.x, center.y];
},
getCenterPoint: function() {
  var leftTop = new fabric.Point(this.left, this.top);
  // 这里的 this.originX, this.originY就是rotate的旋转参考点
  return this.translateToCenterPoint(leftTop, this.originX, this.originY);
},
translateToCenterPoint: function(point, originX, originY) {
  // 根据this.originX, this.originY 计算object中心点center
  var p = this.translateToGivenOrigin(point, originX, originY, 'center', 'center');
  // 计算得到的center点后,再计算旋转后的点坐标
  if (this.angle) {
    return fabric.util.rotatePoint(p, point, degreesToRadians(this.angle));
  }
  return p;
},
rotatePoint: function(point, origin, radians) {
  // 这里计算旋转后的坐标,先减去旋转参考点this.originX, this.originY 的值,然后将得到的值经过旋转radians角度后,再将得到的坐标加上参考点this.originX, this.originY的值,既可得到最终的坐标值
  point.subtractEquals(origin);
  var v = fabric.util.rotateVector(point, radians);
  return new fabric.Point(v.x, v.y).addEquals(origin);
},
rotateVector: function(vector, radians) {
  var sin = fabric.util.sin(radians),
      cos = fabric.util.cos(radians),
      rx = vector.x * cos - vector.y * sin,
      ry = vector.x * sin + vector.y * cos;
  return {
    x: rx,
    y: ry
  };
},
..........  下面这段是对 fabric.util.composeMatrix(this) 说明....................
composeMatrix: function(options) {
  /* 变量说明:
    options => fabric.js 创建的react实例对象,也即是object
  */
  // 平移中心点的矩阵
  var matrix = [1, 0, 0, 1, options.translateX || 0, options.translateY || 0],
      multiply = fabric.util.multiplyTransformMatrices;
  // 平移中心点的矩阵 * 旋转对应的矩阵
  if (options.angle) {
    matrix = multiply(matrix, fabric.util.calcRotateMatrix(options));
  }
  // 平移中心点的矩阵 * 旋转对应的矩阵 * 缩放及拉伸的矩阵
  if (options.scaleX || options.scaleY || options.skewX || options.skewY || options.flipX || options.flipY) {
    matrix = multiply(matrix, fabric.util.calcDimensionsMatrix(options));
  }
  return matrix;
},
// 3*3 的矩阵相乘
multiplyTransformMatrices: function(a, b, is2x2) {
  // Matrix multiply a * b
  return [
    a[0] * b[0] + a[2] * b[1],
    a[1] * b[0] + a[3] * b[1],
    a[0] * b[2] + a[2] * b[3],
    a[1] * b[2] + a[3] * b[3],
    is2x2 ? 0 : a[0] * b[4] + a[2] * b[5] + a[4],
    is2x2 ? 0 : a[1] * b[4] + a[3] * b[5] + a[5]
  ];
},
// 计算旋转的矩阵坐标
calcRotateMatrix: function(options) {
  if (!options.angle) {
    return fabric.iMatrix.concat();
  }
  var theta = fabric.util.degreesToRadians(options.angle),
      cos = fabric.util.cos(theta),
      sin = fabric.util.sin(theta);
  return [cos, sin, -sin, cos, 0, 0];
},
// 计算缩放和拉伸的矩阵坐标
calcDimensionsMatrix: function(options) {
  var scaleX = typeof options.scaleX === 'undefined' ? 1 : options.scaleX,
      scaleY = typeof options.scaleY === 'undefined' ? 1 : options.scaleY,
      scaleMatrix = [
        options.flipX ? -scaleX : scaleX,
        0,
        0,
        options.flipY ? -scaleY : scaleY,
        0,
        0],
      multiply = fabric.util.multiplyTransformMatrices,
      degreesToRadians = fabric.util.degreesToRadians;
  if (options.skewX) {
    scaleMatrix = multiply(
      scaleMatrix,
      [1, 0, Math.tan(degreesToRadians(options.skewX)), 1],
      true);
  }
  if (options.skewY) {
    scaleMatrix = multiply(
      scaleMatrix,
      [1, Math.tan(degreesToRadians(options.skewY)), 0, 1],
      true);
  }
  return scaleMatrix;
},
// 最后将经过平移中心点坐标矩阵 * 旋转对应的矩阵 * 缩放及拉伸的矩阵计算得到的matrix值设置到lower-canvas坐标系中, 如上transform方法所示
ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
//接下来进行元素的绘制, 上面render方法中由于篇幅的原因绘制过程就不展开了
...


到这里终于把fabric.js 绘制元素过程中坐标矩阵的变换说完了,具体的代码细节自行查阅源码吧!


最后


了解矩阵变换的概念,旨在揭开相关开源代码实现的背后细节。


相关文章
|
前端开发
无处不在的前端矩阵变换(上)
在前端开发中不论CSS3也好,SVG也好,还是Canvas,无论多复杂的图形,都是由一个个点组成的。一个元素渲染后就可以得到一张位图,然后对这个位图上每一点进行变换,就可以得到新的一张位图,从而产生了视觉上的平移translate、旋转rotate、缩放scale、拉伸skew 等效果。这一切都是变换矩阵的功劳。
|
1月前
|
Web App开发 缓存 前端开发
前端性能优化的整理笔记(一)
前端性能优化的整理笔记(一)
114 0
|
1天前
|
前端开发 JavaScript 数据库
如何实现前后端分离-----前端笔记
如何实现前后端分离-----前端笔记
|
1天前
|
前端开发 安全 NoSQL
技术笔记:Security前端页面配置
技术笔记:Security前端页面配置
|
25天前
|
JSON 前端开发 JavaScript
前端Ajax、Axios和Fetch的用法和区别笔记
前端Ajax、Axios和Fetch的用法和区别笔记
29 2
|
1月前
|
存储 前端开发 JavaScript
前端笔记_OAuth规则机制下实现个人站点接入qq三方登录
前端笔记_OAuth规则机制下实现个人站点接入qq三方登录
52 1
|
1月前
|
移动开发 前端开发 JavaScript
10款精美的web前端源码的特效,2024年最新面试题+笔记+项目实战
10款精美的web前端源码的特效,2024年最新面试题+笔记+项目实战
|
1月前
|
前端开发 容器
CSS3属性详解(一)文本 盒模型中的 box-ssize 属性 处理兼容性问题:私有前缀 边框 背景属性 渐变 前端开发入门笔记(七)
CSS3属性详解(一)文本 盒模型中的 box-ssize 属性 处理兼容性问题:私有前缀 边框 背景属性 渐变 前端开发入门笔记(七)
40 2
|
1月前
|
移动开发 前端开发 JavaScript
CSS选择器 前端开发入门笔记(十)
CSS选择器 前端开发入门笔记(十)
31 1
|
1月前
|
编解码 前端开发 iOS开发
前端开发入门笔记(八)CSS3属性详解:动画详解+Flex布局图文详解+Web字体
前端开发入门笔记(八)CSS3属性详解:动画详解+Flex布局图文详解+Web字体
70 1