前言

近期在学习 Fabric.js,Fabric.js 的官网文档可谓是一言难尽。没有中文翻译只能算最小的问题,代码举例少的可怜,文档结构十分不友好,搜索功能也不尽人意,很难让人有看下去的耐心。

网络上倒是有挺多不错的学习文章,但是大部分都没有深入介绍,比如在 Fabric.js 第一章的 controls 对象,在我看的文章中都没有见到对其的介绍。

综上所述,在此记录一些个人学习见解,以及通过一些典型案例介绍学习 Fabric.js 的方法。

案例代码有不懂的可以见git:https://github.com/pengzhijian/fabric-learing-demo (强烈推荐,里面还有文章没列出来的部分测试用例

一、安装

yarn add fabric -S
#or
npm i fabric -S

或者下载js到本地使用官网下载链接

注意:用npm下载的一定要下 v5.0版本的,v6.0版本bug很多,而且更新的很多内容和官方文档完全对不上,关键是还没!有!教!程!

建议下载到本地方便查看修改源码。

二、使用

先用基础代码看看效果:

<canvas id="canvas" width="500" height="500"></canvas>
  // 创建一个fabric实例
  const canvas = new fabric.Canvas("canvas");
  // 创建一个矩形对象
  const rect = new fabric.Rect({
    left: 50,
    top: 50,
    width: 50,
    height: 50,
    fill: "red"
  });
  const triangle = new fabric.Triangle({
    left: 100,
    top: 100,
    width: 80,
    height: 40,
    fill: "blue",
  });
  // 添加矩形和三角形到画布
  canvas.add(rect, triangle);

recording.gif

从上图直观可见,Fabric.js由3部分组成,分别是 canvas (画布载体),Object(所画的图形),Controls(选中时的控制器)。而这3项也是 Fabric.js 的几大核心类,下文将着重讲解这几个类的一些应用。

一. Object 类

Fabric.js 提供了很多基础图形:

  1. Circle 圆
  2. Ellipse 椭圆
  3. Line 线
  4. Polygon 多边形
  5. Polyline 折线
  6. Rect 矩形
  7. Triangle 三角形
  8. 文字
  9. 图片

所有基础图形都是 Object类的子类

image.png

Object 类提供了绝大多数基础图形的属性和方法,比如 setget方法,left、top (位置)、width、height (宽高)、fill (填充颜色)、stroke (描边)、angle (角度)、opacity (不透明度)、flip (翻转属性)等。具体有哪些属性方法可以参考官网

注意:官网列举了绝大多数方法和属性,但是并未列全,比如最常用的 set 方法就没有列出来,对于此类情况可以参考我上篇文章通过控制台学习修改Fabric.js源码

可以在我的git仓库项目中查看修改属性值:

请添加图片描述

这些属性和方法的使用很简单,其他文章都有讲解,此处只举例最简单的用法。

  rect.set('fill', 'yellow'); // 修改方块填充颜色
  traingle.set('angle', 45); // 修改三角形角度
  fabric.Object.prototype.opacity = '0.5'; // 修改所有基础对象透明度
  canvas.renderAll(); // 重新渲染

当我们要修改通用的属性方法时,我们可以通过修改 Object 父类实现,如上面的修改透明度。

除此之外我们还可以添加通用属性或方法给 Object,达到修改所有子类的目的,此处举个简单实用的例子讲解。

1. 添加层级方法 getLevel

在 Fabric.js 中是有层级的概念的,在询问过gpt后,得知 Fabric.js 有很多操作层级的方法,但是没有直接的层级属性。

  • Canvas对象层级操作方法

    • canvas.bringToFront(object): 将指定对象移到最前面。
    • canvas.sendToBack(object): 将指定对象移到最后面。
    • canvas.bringForward(object): 将指定对象向前移动一个层级。
    • canvas.sendBackwards(object): 将指定对象向后移动一个层级。
    • canvas.moveTo(object, index): 将指定对象移动到指定的层级索引。
  • Object对象层级操作方法

    • object.bringToFront(): 将当前对象移到最前面。
    • object.sendToBack(): 将当前对象移到最后面。
    • object.bringForward(intersecting): 将当前对象向前移动一个层级,若intersecting为true则会跳过所有交叉的对象。
    • object.sendBackwards(intersecting): 将当前对象向后移动一个层级,若intersecting为true则会跳过所有交叉的对象。
    • object.moveTo(index): 将当前对象移动到指定的层级索引。

想要获取具体图形的层级一般使用 canvas.getObjects().indexOf(xxx)

显然,这个有点麻烦,我们自己加一个 level 方法让其直接显示对象的层级。

// 新增 level 方法
fabric.Object.prototype.getLevel = function() {
  return this.canvas.getObjects().indexOf(this);
}
// 添加到画布
canvas.add(rect, circle, triangle);
// 调用level方法
console.log(rect.getLevel()); // 0
console.log(triangle.getLevel()); // 1

如果觉得这个还是太麻烦了,甚至可以添加一个level属性来显示层级(会稍微复杂一点)。

2. 添加层级属性 level

此处需要修改源码,具体可参考我上篇文章 通过控制台学习修改Fabric.js源码

核心思路:

  1. 先在 Object 类添加level属性。
  2. 写一个方法 setAllLevel 去设置canvas下的所有对象的 level 属性。
  3. 重写 canvas 的 renderAll 方法,在方法的最后加上2中的 setAllLevel方法。

添加 level 属性

// 新增 level 属性
fabric.Object.prototype.level = 0

// 添加对象到画布
canvas.add(rect, circle, triangle);

console.log(rect.level); // 0
console.log(triangle.level); // 0

新增 setAllLevel 方法:

// Canvas 类新增 setAllLevel 方法
canvas.__proto__.__proto__.setAllLevel = function() {
  this.getObjects().forEach((obj, index) => {
    obj.level = index;
  })
}

重写覆盖源码中 canvas 的 renderAll 方法:

// Canvas 类 重写renderAll方法
canvas.__proto__.renderAll = function () {
  console.log('renderAll')
  !this.contextTopDirty ||
    this._groupSelector ||
    this.isDrawingMode ||
    (this.clearContext(this.contextTop), (this.contextTopDirty = !1)),
    this.hasLostContext && this.renderTopLayer(this.contextTop);
  var t = this.contextContainer;
  // 偷个懒,直接用 setTimeout 0 延迟执行
  setTimeout(() => {
    this.setAllLevel();
  })
  return this.renderCanvas(t, this._chooseObjectsToRender()), this;
}

注意:拖动是不会改变物体层级的,只有用上面列出的层级修改方法才会改变层级。

注意:直接在 canvas.add(rect, circle, triangle); 后console level都显示0,因为我偷懒延迟了,在渲染所有图形后再设置level,稍微延迟一点即可得到正确的level属性。

在控制台看结果:

image.png

Object 类还有很多其他功能,比如通用事件监听,动画功能等,官网讲的很详细,论坛里也有很多写的很好的文章,此处就不再做介绍了。

3. 添加自定义子类

在Fabric.js中,几乎所有的2d图形直接或间接继承自 Object 类,那么如果我们不用其自带的2d图形,而是自建图形,要怎么应用 Fabric.js 中的方法呢?

Fabric.js 提供了 fabric.util.createClass 方法解决这个问题

先看看一个自定义子类的结构:

      // 创建一个自定义子类
      const customClass = fabric.util.createClass(fabric.Object, {
        type: "customClass",
        initialize: function (options) {
          options || (options = {});
          this.callSuper("initialize", options);
          // 自定义属性
        },

        toObject: function () {
          return fabric.util.object.extend(this.callSuper("toObject"), {
            // 将自定义属性添加到序列化对象中
          });
        },

        _render: function (ctx) {
          this.callSuper("_render", ctx);
          // 自定义渲染逻辑
        },
      });

一个简单的自定义类主要要修改3个地方,分别是:

  1. initialize : 添加的自定义属性方法放这
  2. toObject: 将自定义属性添加到序列化对象中,方便canvas记录
  3. _render: 处理自定义渲染逻辑

此处举一个简单的例子,写一个自定义图形类 Map 网格图:

新增绘制网格图的方法 initMap:

      /* 
        绘制网格横竖线map
      */
      function initMap(options, ctx) {
        const { gridNumX, gridNumY, width, height, fill, left, top } = options;
        ctx.save();
        ctx.translate(-width / 2, -height / 2)
        // 开始路径并绘制线条
        ctx.beginPath();
        // 设置线条样式
        ctx.lineWidth = 1;
        ctx.strokeStyle = fill;
        // 开始绘制横线
        for (let i = 0; i < gridNumY + 1; i++) {
          // 注意要算线的宽度,也就是后面那个+i
          ctx.moveTo(0, height / gridNumY * i);
          ctx.lineTo(width, height / gridNumY * i);
          ctx.stroke();
        }
        // 开始绘制竖线
        for (let i = 0; i < gridNumX + 1; i++) {
          ctx.moveTo(width / gridNumX * i, 0);
          ctx.lineTo(width / gridNumX * i, height);
          ctx.stroke();
        }
        ctx.restore();
      }

创建 Map 子类:

      // 创建一个自定义子类
      const Map = fabric.util.createClass(fabric.Object, {
        type: "Map",
        initialize: function (options) {
          options || (options = {});
          this.callSuper("initialize", options);
          this.set("gridNumX", options.gridNumX || "");
          this.set("gridNumY", options.gridNumY || "");
        },

        toObject: function () {
          return fabric.util.object.extend(this.callSuper("toObject"), {
            gridNumX: this.get("gridNumX"),
            gridNumY: this.get("gridNumY"),
          });
        },

        _render: function (ctx) {
          this.callSuper("_render", ctx);
          initMap({
            ...this
          }, ctx)
        },
      });

新建 map 实例并添加到canvas:

      const map = new Map({
        left: 100,
        top: 100,
        label: "test",
        fill: "#faa",
        width: 100,
        height: 100,
        gridNumX: 4,
        gridNumY: 3
      });

      const map2 = new Map({
        left: 300,
        top: 100,
        label: "test",
        fill: "green",
        width: 200,
        height: 300,
        gridNumX: 2,
        gridNumY: 5
      });
      // 将所有图形添加到 canvas 中
      canvas.add(map, map2);

如图所示,成功创建了可复用的自定义图形,而且能够使用 Object 类的功能。

在这里插入图片描述

二、Controls 类

当 Fabric.js 画布中的对象被选中时,对象的四周会出现一些小方框,我们可以选中方块进行缩放、旋转操作。

这些被选中后出现的小方框被称为控制器 Controls。fabric.Object 类中提供了可以修改的 controls 对象,我们可以通过修改该对象达到修改控制器样式、修改控制器功能、亦或者添加新控制器的目的。

1. 修改 controls

修改之前我们先在控制台看看 Object 的 controls 属性有什么:

image.png

从图中可得知,controls 默认有9个属性,对应着对象被选中的9个小方框,其中的 t 是 top 的缩写,l 是 left 的缩写, m 是 middle 的缩写, r 是 right 的缩写 b 是 bottom 的缩写。我们可以通过名字快速得知该控制器的位置。

1.1 删除 control

先删除一个控制器试试水。删除右下角的控制器:

    // 创建一个矩形对象
    let rect = new fabric.Rect({
      left: 100,
      top: 100,
      fill: 'red',
      width: 100,
      height: 100,
    });
    // 创建一个矩形对象
    let circle = new fabric.Circle({
      left: 200,
      top: 200,
      fill: 'blue',
      radius: 50,
    });
    delete rect.controls.br; // 删除方块右下角的控制点
    // 添加矩形到画布
    canvas.add(rect, circle);

recording.gif

修改后发现方块的右下角控制器没了,但是圆还有合并后的图形右下角控制器都没了。在 Fabric.js 中,当修改对象的 controls 时,默认情况下所有相同类型的对象会共享相同的 controls。要确保只修改特定对象的 controls 而不影响其他对象,需要克隆默认的 controls 并在克隆的基础上进行修改。

    // 克隆默认的controls
    rect.controls = fabric.util.object.clone(fabric.Rect.prototype.controls);
    // 删除矩形右下角的控制点
    delete rect.controls.br;

recording.gif

1.2 修改 control 样式

Fabric.js 官方提供了一些修改 controls 样式的属性:

    // 创建一个矩形对象
    let rect = new fabric.Rect({
      left: 100,
      top: 100,
      fill: 'red',
      width: 100,
      height: 100,
      cornerStyle: 'circle',  // 设置控制点样式为圆形
      cornerColor: 'blue',    // 设置控制点颜色
      cornerSize: 10,         // 设置控制点大小
      hasBorders: true, // 设置是否显示边框
      borderColor: 'purple',  // 设置控制线的颜色
      borderDashArray: [5, 5], // 设置控制线的虚线样式
      borderScaleFactor: 0, // 设置控制线的宽度
      borderOpacityWhenMoving: 0.9, // 设置控制线的透明度
    });

image.png

显而易见的是,官方提供的样式修改依旧难看,而且可供修改的属性很少。

要想自定义修改控制器样式,先需要了解 Control 类。所有控制器都继承自 Control 对象,在 Control 对象中的 render 函数负责渲染控制器图形。

image.png

image.png

要想修改样式,直接修改 render 方法即可:

将左上角修改为小圆点

    // 克隆默认的controls
    circle.controls = fabric.util.object.clone(fabric.Rect.prototype.controls);
    // 为了不所有对象全部修改,同样需要对其tl控制点进行克隆
    circle.controls.tl = fabric.util.object.clone(fabric.Object.prototype.controls.tl);
    // 将左上角控制点样式修改为一个实心小圆点 
    circle.controls.tl.render = function(ctx, left, top, styleOverride, fabricObject) {
      ctx.save();
      ctx.fillStyle = 'rgb(78, 172, 189)';
      ctx.beginPath();
      ctx.arc(left, top, 8, 0, Math.PI * 2, true);
      ctx.fill();
      ctx.restore();
    }

recording.gif

1.3 修改 control 功能

Control 类中提供了鼠标拖动和鼠标按下抬起的监听事件,我们可以通过修改这些事件达到修改功能的目的。

先在控制台中看看这些事件方法:
image.png

上图可以看到 ml 控制器只用了 actionHandler 方法,且方法的参数有4个,接下来尝试修改 actionHandler 方法:

    circle.controls.tl.actionHandler = function(eventData, transform, x, y) {
      console.log('自定义控制点的行为', eventData, transform, x, y);
      // 自定义控制点的行为
      // 这里可以做一些自定义的操作,比如修改圆的颜色
      transform.target.set('fill', `rgb(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255})`);
      canvas.renderAll();
    }

代码修改后拖动左上角不再是缩放,而是改变成随机颜色。

在这里插入图片描述

2. 新增自定义 controls

上面的例子都是修改原来就有的 control ,一般而言我们会去修改他们的样式,但是对于功能更倾向于新增控制点,而不是修改原来的 control。接下来我们尝试新增一个 control。

control 类的属性方法很少,文档理所当然的没有讲解,可以自行在控制台查看,以下列举一些常改的属性:

    // 创建一个控件
    const customControl = new fabric.Control({
      // 相对于中心点的偏移量,1指偏移一整个width
      x: 1,
      y: 0,
      cursorStyle: 'pointer',
      // 鼠标按下事件
      mouseDownHandler: (eventData, transform, x, y) => {
      },
      // 鼠标滑动事件
      actionHandler: (eventData, transform, x, y) => {
      },
      // 控件名字
      actionName: 'test',
      // 自定义渲染
      render: function(ctx, left, top, styleOverride, fabricObject) {
      }
    });

举一个简单的删除按钮例子讲解:

2.1 自定义删除控件

编写思路:1. 位置在右上角 2. 图形是一个红色小x 3. 点击弹窗提示是否删除,是的话就删掉。

只要对Control属性做简单修改即可:

    // 创建一个控件
    const deleteControl = new fabric.Control({
      // 相对于中心点的偏移量,1指偏移一整个width
      x: 0.7,
      y: -0.7,
      sizeX: 30, // 控件的宽度
      sizeY: 30,
      cursorStyle: 'pointer',
      // 鼠标按下事件
      mouseDownHandler: (eventData, transform, x, y) => {
        if (confirm('确定删除?')) {
          canvas.remove(transform.target);
        }
        canvas.renderAll();
      },
      // 控件名字
      actionName: 'delete',
      // 自定义渲染
      render: function(ctx, left, top, styleOverride, fabricObject) {
        ctx.save();
        ctx.translate(left, top);
        ctx.fillStyle = 'red';
        ctx.font = '20px Arial';
        ctx.fillText('X', -10, 10); // 确保文字在控件的中心
        ctx.restore();
      }
    });

    // 将控件添加到圆的controls对象中
    circle.controls.delete = deleteControl

如图所示,成功添加了删除按钮:

在这里插入图片描述

三、Canvas 类

Canvas类作为图形的底座有很多功能,最常用的有 renderAll (渲染所有图形),add(添加图形对象),remove(删除图形对象)等方法。大部分的用法都比较简单,有不懂用什么方法的直接问gpt即可。

此处仅介绍一些关于Canvas类典型案例。

1. 平移和缩放

平移和缩放相信熟悉 canvas 原生属性的小伙伴都不陌生,这俩功能都不难实现。Fabric.js 提供了专门的方法,让平移和缩放功能变得更加简单。

1.1 缩放

Canvas类缩放相关方法有三个:

  • setZoom(zoom: number) :

    • 设置整个画布的缩放比例。
    • 例如:canvas.setZoom(2); 将画布的缩放比例设置为 2 倍。
  • zoomToPoint(point: fabric.Point, value: number) :

    • 以特定点为中心缩放画布。
    • 例如:canvas.zoomToPoint(new fabric.Point(100, 100), 2); 将画布以 (100, 100) 点为中心缩放到 2 倍。
  • getZoom() :

    • 获取当前画布的缩放比例。
    • 例如:let currentZoom = canvas.getZoom(); 将当前缩放比例赋值给 currentZoom

举个简单的例子,滚轮滚动时光标中心缩放:

      canvas.on("mouse:wheel", function (option) {
        // 判断是放大还是缩小
        const delta = option.e.deltaY;
        let zoom = canvas.getZoom();
        zoom *= 0.999 ** delta;
        if (zoom > 20) zoom = 20;
        if (zoom < 0.01) zoom = 0.01;
        // 在鼠标位置缩放
        canvas.zoomToPoint({ x: option.e.offsetX, y: option.e.offsetY }, zoom);
        option.e.preventDefault();
        option.e.stopPropagation();
      });
1.2 平移

Canvas类平移相关的方法有:

  • absolutePan(point: fabric.Point) :

    • 绝对移动画布视口到指定的点。
    • 例如:canvas.absolutePan(new fabric.Point(100, 100)); 将画布的视口移动到 (100, 100)。
  • relativePan(point: fabric.Point) :

    • 相对当前视口的位置移动画布的视口。
    • 例如:canvas.relativePan(new fabric.Point(50, 50)); 将画布的视口相对当前视口位置向右下方移动 50 像素。
  • viewportTransform:

    • 一个 6 元素数组,表示画布的当前视口变换矩阵。
    • 可以直接操作此数组来进行高级平移和变换。
    • 例如:canvas.viewportTransform[4] = 100; canvas.viewportTransform[5] = 100; 将画布的视口移动到 (100, 100)。
  • setViewportTransform(transform: number[]) :

    • 设置画布的视口变换矩阵。
    • 参数含义:[scaleX, skewX, skewY, scaleY, translateX, translateY]
    • 例如:canvas.setViewportTransform([1, 0, 0, 1, 100, 100]); 将画布的视口变换设置为平移到 (100, 100)。

举个简单的例子,按下alt键拖动画布:

      // 当鼠标按下时
      canvas.on('mouse:down', function(option) {
        const evt = option.e;
        console.log(evt)
        if (evt.altKey === true) {  // 检查是否按下alt键
          this.isDragging = true;
          this.selection = false;
          this.lastPosX = evt.clientX;
          this.lastPosY = evt.clientY;
        }
      });

      // 当鼠标移动时
      canvas.on('mouse:move', function(option) {
        if (this.isDragging) {
          const e = option.e;
          const vpt = this.viewportTransform;
          vpt[4] += e.clientX - this.lastPosX;
          vpt[5] += e.clientY - this.lastPosY;
          this.requestRenderAll();
          this.lastPosX = e.clientX;
          this.lastPosY = e.clientY;
        }
      });

      // 当鼠标松开时
      canvas.on('mouse:up', function(option) {
        // 在鼠标松开时重新计算所有对象的交互
        this.setViewportTransform(this.viewportTransform);
        this.isDragging = false;
        this.selection = true;
      });

2. 获取真实转换坐标

在图像处理的过程中,我们经常会用到坐标点信息,以便于进行一些交互操作。

此处举一个简单的例子,当鼠标点击时,在鼠标的位置创建一个方块对象:

      // 当鼠标按下时
      canvas.on('mouse:down', function(option) {
        const evt = option.e;
        // 创建一个小方块
        this.add(new fabric.Rect({
          left: evt.offsetX,
          top: evt.offsetY,
          width: 50,
          height: 50,
          fill: 'yellow'
        }))
        this.renderAll();
      });

请添加图片描述

从上图可见,当canvas未平移或缩放时,可以很简单的获取相应点位置,但是一但平移或者缩放后,鼠标点的位置就全乱了。Fabric.js 提供了 transformPoint 方法解决这一问题。

  • fabric.util.transformPoint(Point, transform):

    • 将Canvas坐标点转换为视口坐标点
    • 例如:fabric.util.transformPoint(new fabric.Point(100, 100), canvas.viewportTransform),将视口的(100,100)坐标点转化为平移缩放后的坐标点。
  • Canvas.getPointer(event):

    • 用于获取事件(如鼠标或触摸事件)发生时相对于画布的坐标。它考虑了当前视口的变换(包括平移和缩放),因此可以正确地将鼠标或触摸事件的屏幕坐标转换为画布坐标。

修改代码:

      // 当鼠标按下时
      canvas.on('mouse:down', function(option) {
        const evt = option.e;
        // 用transformPoint创建一个小方块
        // 注意 transformPoint 作用是将一个坐标从一个坐标系转换到另一个坐标系
        // 由于这里的将按下的视口坐标转换成 canvas画布坐标系,所以需要用 invertTransform 反转变换
        this.add(new fabric.Rect({
          left: fabric.util.transformPoint({ x: evt.offsetX, y: evt.offsetY },  fabric.util.invertTransform(canvas.viewportTransform)).x,
          top: fabric.util.transformPoint({ x: evt.offsetX, y: evt.offsetY }, fabric.util.invertTransform(canvas.viewportTransform)).y,
          width: 50,
          height: 50,
          fill: 'red'
        }))
        // 用getPointer创建一个小方块
        const pointer = canvas.getPointer(evt);
        console.log('potint, ', pointer)
        this.add(new fabric.Rect({
          left: pointer.x,
          top: pointer.y,
          width: 50,
          height: 50,
          fill: 'blue'
        }))
        this.renderAll();
      });

注意 transformPoint 作用是将一个坐标从一个坐标系转换到另一个坐标系,由于这里的将按下的视口坐标转换成 canvas画布坐标系,所以需要用 invertTransform 反转变换。

请添加图片描述

如图所示,两种方法都成功在正确位置创建了方块。

3. 保存和导入导出

3.1 保存为图片

Fabric.js 提供了canvas.toDataURL 方法导出保存为图片:

      function exportImage() {
        const dataURL = canvas.toDataURL({
          format: 'png', // 图片格式
          quality: 1, // 图片质量
          multiplier: 2, // 图片放大倍数
          left: 0, // 裁剪区域左上角x坐标
          top: 0, // 裁剪区域左上角y坐标
          width: canvas.width, // 裁剪区域宽度
          height: canvas.height, // 裁剪区域高度
          cropX: 0, // 裁剪导出图片的起始 X 坐标
          cropY: 0, // 裁剪导出图片的起始 Y 坐标
          cropWidth: canvas.width, // 裁剪导出图片的裁剪宽度
          cropHeight: canvas.height // 裁剪导出图片的裁剪高度
        })

        // 创建下载链接
        let downloadLink = document.createElement('a');
        downloadLink.href = dataURL;
        downloadLink.download = 'canvas-image.jpg';
        downloadLink.click();
      }

保存图片通常很实用,因为大部分人使用画图的目的都是为了保存图片,但是对于开发者和绘图者来说,图片的资源占用太大了,而且一但存储为图片就意味着整个画布会被像素化,没有办法很好的进行二次开发。对于此种情况,Fabric.js提供了序列化字符串和svg的导入导出方法。

3.2 导出序列化字符串

Fabric 中序列化相关方法主要是 fabric.Canvas.toObject()fabric.Canvas.toJSON(),两个方法的用法一样,只是 toJSON 进行了字符串压缩,toObject 没有。

由于在 ES5 中使用 JSON.stringify() 方法会隐式调用传递对象上的 toJSON 方法(如果该方法存在),Fabric 中的 canvas 实例具有 toJSON 方法,因此我们可以直接对其进行 stringify 调用:

const canvas = new fabric.Canvas('canvas');
JSON.stringify(canvas); // '{"objects":[],"background":"rgba(0, 0, 0, 0)"}'

在 Fabric 中的每次操作都会记录下来,比如新建一个方块:

// 创建一个矩形对象
let rect = new fabric.Rect({
  left: 100,
  top: 100,
  fill: 'red',
  width: 100,
  height: 100,
});

// 添加矩形到画布
canvas.add(rect);
console.log(JSON.stringify(canvas));

输出:

'{"version":"4.5.0","objects":[{"type":"rect","version":"4.5.0","originX":"left","originY":"top","left":100,"top":100,"width":100,"height":100,"fill":"red","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"rx":0,"ry":0}]}'

反序列化导入使用 Canvas.loadFromJSONloadFromDatalessJSON 方法:

canvas.loadFromJSON('{"version":"4.5.0","objects":[{"type":"rect","version":"4.5.0","originX":"left","originY":"top","left":100,"top":100,"width":100,"height":100,"fill":"red","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"rx":0,"ry":0}]}')
3.3 导出svg格式

使用 toSVG 导出,导出使用 fabric.loadSVGFromURLfabric.loadSVGFromString 方法

代码演示:

导出:

canvas.add(new fabric.Rect({
  left: 50,
  top: 50,
  height: 20,
  width: 20,
  fill: 'green'
}));
console.log(canvas.toSVG());

输出:

'<?xml version="1.0" standalone="no" ?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800" height="700" xml:space="preserve"><desc>Created with Fabric.js 0.9.21</desc><rect x="-10" y="-10" rx="0" ry="0" width="20" height="20" style="stroke: none; stroke-width: 1; stroke-dasharray: ; fill: green; opacity: 1;" transform="translate(50 50)" /></svg>'

导入:

      fabric.loadSVGFromString('<?xml version="1.0" standalone="no" ?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800" height="700" xml:space="preserve"><desc>Created with Fabric.js 0.9.21</desc><rect x="-10" y="-10" rx="0" ry="0" width="20" height="20" style="stroke: none; stroke-width: 1; stroke-dasharray: ; fill: green; opacity: 1;" transform="translate(50 50)" /></svg>', function (objects, options) {
        // 创建一个Group对象,将加载的SVG对象组装到一起
        let svgGroup = fabric.util.groupSVGElements(objects, options);

        // 设置SVG对象的位置和缩放
        svgGroup.set({
          left: 100,
          top: 100,
          scaleX: 1,
          scaleY: 1,
        });

        // 将SVG对象添加到画布
        canvas.add(svgGroup);

        // 渲染画布
        canvas.renderAll();
      });

总结

此文算是我几个星期学习Fabric.js的深入总结,Fabric.js的功能很强大,依旧有很多内容值得去探索,在学习期间发现了很多做的很好的Fabric.js二开项目。但是在深入了解和学习Fabric.js后,发现可能并不是所有项目都适合 Fabric.js(别人的二开项目做的太好了,我就不凑热闹了),所以我接下来的项目会直接用原生 Canvas 制作,此处学习算是告一段落了,希望我总结的教程对您有用,点赞多的话后续还会出新的教程()

前置相关文章:

通过控制台学习修改Fabric.js源码

如何用canvas实现fabricjs(图层、拖拽、旋转、拉伸)功能

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐