我从 fabric.js 中学到了什么

媒介

熟习 canvas 的朋侪想必都运用或许听说过 Fabric.js,Fabric 算是一个元老级的 canvas 库了,从第一个版本宣布到如今,已经有 8 年时刻了。我近一年时刻也在项目中运用,作为用户简朴说说感觉:

  1. 轻易,只要想不到,没有做不到
  2. 源码写的真好,代码范例,诠释清楚
  3. 社区真匮乏,国内资本特别少
  4. 看文档不如看源码

优缺点都很鲜亮,但总的来讲,假如你要做一个在线编辑类的项目,比方在线 PPT,在线制图等运用,fabric 相对是个很好的挑选。

那末这一系列文章要写什么?这里不会重要引见如何运用 fabric,重要写的内容是把在浏览源码过程当中,把涉及到原理相干的学问总结出来,比方相干图形学学问、canvas 相干、fabric 中的设想头脑等的相干学问。所以,假如你如今还对 fabric 不是很相识,发起先去官网找几个 demo 试一下。

下面我们进入此次的正题,这篇文章重要引见 fabric.canvas 涉及到的部分内容。

从建立画布最先

fabric 建立画布很简朴:

const canvas = new fabric.Canvas("domId", options);

在如许一行代码背地,fabric 重要做了下面这几件事变:

  • 建立缓存 canvas
  • 构建两层 canvas 元素:lower-canvas 和 upper-canvas
  • 绑定事宜
  • 处置惩罚 retina 屏

下面我把相干内容逐一论述。

canvas 缓存

引见 canvas 缓存,fabric 中的缓存也是相似的原理,简朴来讲,就是运用一个离屏 canvas 来做预衬着,在实在画布上用 drawImage 替代直接绘制图形

我们先来看个 例子,人人能够把 FPS meter 翻开,切换按钮能够看到,不运用缓存和运用缓存 FPS 值差异照样挺大的,我电脑在运用缓存的时刻基础在 60fps,不运用会降到 15fps 摆布。人人能够翻开掌握台或许在 这里 检察代码。
下面列出重要的代码片断:

class Ball {
  constructor(x, y, vx, vy, useCache = true) {
    // ...
    if (useCache) {
      this.useCache = useCache;
      this.cacheCanvas = document.createElement("canvas");
      // 离屏 canvas 宽高取要衬着图形的宽高,不能够取实在 canvas 的宽高,不然会衬着大批无用地区
      this.cacheCanvas.width = 2 * (this.r + BORDER_WIDTH);
      this.cacheCanvas.height = 2 * (this.r + BORDER_WIDTH);
      this.cacheCtx = this.cacheCanvas.getContext("2d");
      this.cache();
    }
  }

  paint() {
    // 运用缓存直接运用建立的离屏canvas,不然直接绘制图形
    if (!this.useCache) {
      ctx.save();
      ctx.lineWidth = BORDER_WIDTH;
      ctx.beginPath();
      ctx.strokeStyle = this.color;
      ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
      ctx.stroke();
      ctx.restore();
    } else {
      ctx.drawImage(
        this.cacheCanvas,
        this.x - this.r,
        this.y - this.r,
        this.cacheCanvas.width,
        this.cacheCanvas.height
      );
    }
  }

  move() {
    // ...
  }

  cache() {
    // 绘制图形
    this.cacheCtx.save();
    this.cacheCtx.lineWidth = BORDER_WIDTH;
    this.cacheCtx.beginPath();
    this.cacheCtx.strokeStyle = this.color;
    this.cacheCtx.arc(
      this.r + BORDER_WIDTH,
      this.r + BORDER_WIDTH,
      this.r,
      0,
      2 * Math.PI
    );
    this.cacheCtx.stroke();
    this.cacheCtx.restore();
  }
}

诠释一下两者区分:

  • 运用缓存:在实例化每一个图形的时刻(衬着之前),先将图形衬着到一个离屏的 canvas 上,在衬着的时刻,直接用 drawImage 将离屏的 canvas 衬着。
  • 不运用缓存: 在衬着的时刻直接绘制图形

运用缓存的时刻,有一点须要注重的是要掌握好离屏 canvas 的大小,不能够直接取和衬着 canvas 的现实宽高,不然会衬着许多无用的空间,比方上面例子中每一个离屏 canvas 的宽高只须要和对应图形的宽高一致。

this.cacheCanvas.width = 2 * (this.r + BORDER_WIDTH);
this.cacheCanvas.height = 2 * (this.r + BORDER_WIDTH);

上述代码中重要节省时刻的处所在 paint 函数中运用 drawImage会比直接绘制图形节省时刻,那末是不是一切场景都是如许呢?我们再来看下面这个 例子.

这个例子和上面的只要绘制图形的代码差别:

// 从庞杂图形变成了简朴图形
cache() {
  this.cacheCtx.save();
  this.cacheCtx.lineWidth = BORDER_WIDTH;
  this.cacheCtx.beginPath();
  this.cacheCtx.strokeStyle = this.color;
  this.cacheCtx.arc(
    this.r + BORDER_WIDTH,
    this.r + BORDER_WIDTH,
    this.r,
    0,
    2 * Math.PI
  );
  this.cacheCtx.stroke();
  this.cacheCtx.restore();
}

只是cache要领中把庞杂图形变成了简朴的图形。但现实结果相差甚远,运用缓存和不运用性能差异并不大,以至不运用时 fps 值还更高一些。

所以看来图形的庞杂度,直接会影响 canvas 缓存的结果,我们在开辟过程当中,也不能自觉引入缓存,要权衡利弊。fabric 中缓存是默许开启的,同时也能够设置 objectCaching 为 false 禁用。

lower-canvas 和 upper-canvas

假如人人仔细的话应该会发明,当我们实行new fabric.Canvas('domeId')的时刻,在页面上 dom 元素就改变了,fabric 复制了一层 canvas 盖在了我们定义的 canvas 上面:

《我从 fabric.js 中学到了什么》

fabric 如许设想将衬着层和交互层做了星散,lower-canvas 只担任衬着元素;一切的交互,比方框选,事宜处置惩罚都在 upper-canvas 上。

趁便提一下,fabric 供应了衬着静态画布的要领,假如你的画布不须要任何交互,只用来展现,那末能够用new fabric.StaticCanvas('domId', options)来初始化,这时刻 dom 构造中就只要一个 canvas,没有 upper-canvas 了。

说到这里,许多同砚能够会想到,事宜是如何绑定的呢?实在两个 canvas 大小等属性都是一致的,所以坐标也是能够对应上的,比方在 upper-canvas 上某个位置点击了一下,那末就能够去 lower-canvas 上就能够用这个坐标去找是不是点击到了一个元素,那末题目来了,如何推断一个点在一个图形中呢?

如何推断点在图形中

这个题目网上有个比较广泛的计划,就是经由过程画一条射线,经由过程交点奇偶性来推断。如下图:

《我从 fabric.js 中学到了什么》

  1. 设目标点 P,使 P 点向恣意一个方向画一条射线,保证不与图形的极点订交;
  2. 纪录射线与图形的交点数目 n;
  3. n 为奇数时,P 就在图形内,反之则在图形外。

而 fabric 中并没有效这类要领,缘由很简朴,这个算法是有条件的:发出的射线不能与图形任何极点订交。 这个条件关于我们主观来推断是很简朴的,但顺序中处置惩罚能够就须要大批的代码去推断是不是与交点订交,假如订交再从新天生一条射线。

fabric 中运用的算法对上述算法进行了革新,我们连系下图来诠释:

《我从 fabric.js 中学到了什么》

个中 e1 ~ e5 分别为多边形的边,P 为目标点,黑色实心点为多边形的极点,r 为 P 延 X 轴发出的射线(差别于上面的要领,这里我们商定 r 射线只能延 X 轴发出)。

  1. 设目标点 P,使 P 延 X 轴方向画一条射线( y=Py ),设 intersectionCount = 0
  2. 遍历多边形的一切边,设边的极点为 p1, p2

    1. 假如 p1y < Py,而且 p2y < Py,跳过(也就是这条边在 P 点下方)
    2. 假如 p1y >= Py,而且 p2y >= Py,跳过(也就是这条边在 P 点上方)
    3. 不然,设射线与这条边的交点为 S,假如 Sx >= Px,intersectionCount加 1
  3. 终究假如intersectionCount为奇数,则在图形内,反之则在图形外。

推断的部分用代码完成相似:

// point 目标点,lines多边形的一切边
function checkPoint(point, lines) {
  let intersectionCount = 0;
  let { x, y } = point;
  for (let i = 0; i < lines.length; i++) {
    let line = lines[i];
    // 两个极点
    let { p1, p2 } = line;
    if ((p1.y < y && p2.y < y) || (p1.y >= y && p2.y >= y)) {
      continue;
    } else {
      const sx = ((y - p1.y) / (p2.y - p1.y)) * (p2.x - p1.x) + p1.x;
      if (sx >= x) {
        intersectionCount++;
      }
    }
  }
  return intersectionCount % 2 === 0;
}

这里是个简朴的例子。同时 这里 能够猎取完全代码。

处置惩罚 Retina 屏

Retina 屏幕隐约的题目,直接给出处置惩罚要领,就不睁开说了。

  1. canvas.width, canvas.height 放大至 dpi 倍
  2. canvas.style.width, canvas.style.height 设为原始 canvas 宽高
  3. ctx 缩放 dpi 倍

代码:

function initRetina(canvas, ctx) {
  const dpi = window.devicePixelRatio;
  canvas.style.width = canvas.width + "px";
  canvas.style.height = canvas.height + "px";
  canvas.setAttribute("width", canvas.width * dpi);
  canvas.setAttribute("height", canvas.height * dpi);
  ctx.scale(dpi, dpi);
}

检察例子完全代码

小结

本篇文章重要针对fabric.canvas模块,引见了相干 canvas 缓存,fabric 中推断点在图形中的算法以及如何处置惩罚 retina 屏幕的学问,作为系列的第一篇文章,能够会有许多题目,若有毛病及看法,迎接批评指正。

参考文献:

http://idav.ucdavis.edu/~okre…

http://www.geog.ubc.ca/course…

https://www.cnblogs.com/axes/…

http://fabricjs.com/docs/

    原文作者:LeapFE
    原文地址: https://segmentfault.com/a/1190000019054853
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞