一,fabric对象的理解

原生的canvas画图操作太过繁琐,fabric.js在原生canvas方法之上封装了一层,提供更为简单但功能强大的对象模型。它负责画布的状态和渲染,并让我们直接使用“对象”。

使用abric需要先理解它对象的概念。画布是一个对象,上面的各个图案也是对象。而原生的canvas则没有这么多对象。可以看下文对比,理解一下。

1,使用原生canvas和fabric创建图形对比

当我们使用canvas画图的操作:

<template>
  <div class="app">
    <canvas id="canvas" width="300" height="200"></canvas>
  </div>
</template>

<script>
export default {
  mounted() {
    var canvasEl = document.getElementById("canvas");
    canvasEl.style.background = 'grey'
    var ctx = canvasEl.getContext("2d");
    ctx.fillStyle = 'red';
    ctx.fillRect(100,100,20,20);
  },
};
</script>

看起来和原生js差不多,繁琐低效。再看fabric:

<template>
  <div class="app">
    <canvas id="canvas" width="300" height="200"></canvas>
  </div>
</template>

<script>
import {fabric} from "fabric"
export default {
  mounted(){
    var canvas = new fabric.Canvas("canvas",{backgroundColor:"grey"});
    var rect = new fabric.Rect({
      left:100,
      top:100,
      fill:"red",
      width:20,
      height:20,
    });
    canvas.add(rect);
  }
}
</script>

使用原生方法,我们可以在上下文ctx(一个表示整个画布位图的对象)上操作。在Fabric中,我们对对象进行操作——实例化它们,更改其属性并将其添加到画布。这些对象是Fabric土地上的一等公民。

2,使用原生canvas和fabric修改图形对比

按照1中的说法,原生canvas中我们只能获取到表示整个画布位图的ctx对象。那如果要移动画布中的图案呢?

原生canvas需要先清除画布上的内容,然后再在目标位置画出新内容。

mounted() {
    var canvasEl = document.getElementById("canvas");
    canvasEl.style.background = 'grey'
    var ctx = canvasEl.getContext("2d");
    ctx.fillStyle = 'red';
	ctx.fillRect(100,100,20,20);
    //位移
    ctx.clearRect(0,0,canvasEl.width,canvasEl.height);//整个画布内容擦除
    ctx.fillRect(20,50,20,20);//新位置绘图
  },

而使用fabric呢?

mounted(){
    var canvas = new fabric.Canvas("canvas",{backgroundColor:"grey"});
    var rect = new fabric.Rect({
      left:100,
      top:100,
      fill:"red",
      width:20,
      height:20,
    });
    canvas.add(rect);//在画布对象上添加这个矩形
	// 位移
    rect.set({left:20,top:50});//修改举行对象的位置属性
    canvas.renderAll()//修改完之后重新渲染
  }

画布上创建的矩形rect本身是一个对象,我们只要修改它的位置属性,就可以实现位置的移动了。理解了fabric的对象的概念,就可以开始使用了。

二,安装

npm i fabric --save

三,画布对象

<canvas width="400" height="400" id="canvas" style="border: 1px solid #ccc;"></canvas>
import { fabric } from 'fabric'

const canvas=new fabric.Canvas("canvas", {
      backgroundColor: "pink",
      selectionColor: "blue",
      selectionLineWidth: 2,
      // ...
    }); // 这里传入的是canvas的id:c,第二个参数是对象,里面可以设置画布的一些属性
//获取canvas上的所有对象
canvas.getObjects(); 
//在画布上添加指定的对象
canvas.add(rect); 
//删除指定的对象
canvas.remove(rect); 
}

参数的设置可以查看官网对应的api:JSDoc: Class: Canvas (fabricjs.com)

四,创建基础对象

1,Fabric.js 基础对象

Fabric.js 中有以下七种预定义的基础对象。

fabric.Rect 矩形
fabric.Circle 圆形
fabric.Triangle 三角形
fabric.Ellipse 椭圆
fabric.Line 线型
fabric.Polyline 折线
fabric.Polygon 多边形

为了方便起见,我把这七种都写出来了:

//新建画布对象
    const canvas = new fabric.Canvas("canvas", {
      backgroundColor: "pink",
      selectionColor: "blue",
      selectionLineWidth: 2,
    });
    //矩形
    const rect = new fabric.Rect({
      left: 10,
      top: 10,
      width: 30,
      height: 30,
      fill: "yellow",
    });
    //圆形
    const circle = new fabric.Circle({
      left: 50,
      top: 10,
      radius: 15,
      fill: "green",
    });
    //三角形
    const triangle = new fabric.Triangle({
      left: 90,
      top: 10,
      width: 30,
      height: 30,
      fill: "blue",
    });
    //椭圆
    const ellipse = new fabric.Ellipse({
      left: 130,
      top: 10,
      rx: 10, //短轴
      ry: 15, //长轴
      fill: "red", //填充颜色
      stroke: "rgba(0, 0, 0, 1)", //边框颜色
    });
    //直线
    const line = new fabric.Line([170, 25, 200, 25], {
      //该数组四个值【起点x,起点y,终点x,终点y】
      fill: "#5E2300", //填充颜色
      stroke: "#5E2300", //笔触颜色
    });
    //折线
    const polyline = new fabric.Polyline(
      [
        {
          x: 200,
          y: 10,
        },
        {
          x: 250,
          y: 50,
        },
        {
          x: 250,
          y: 180,
        },
      ],
      {
        fill: "transparent",
        stroke: "red", //笔触颜色
        strokeWidth: 2,
      }
    );
    // 多边形
    const polygon = new fabric.Polygon(
      [
        {
          x: 200,
          y: 10,
        },
        {
          x: 250,
          y: 50,
        },
        {
          x: 250,
          y: 180,
        },
      ],
      {
        left: 100,
        top: 50,
        fill: "red",
        strokeWidth: 4,
        stroke: "green",
      }
    );
    canvas.add(rect, circle, triangle, ellipse, line, polyline, polygon); //它可以添加一个,也可以一次性添加多个

实现的效果:

fabricjs 如何变形_前端

2,基础对象的常见属性

上文创建了其中基础对象,他们有一些属性参数,如下所示:

fill: "#5E2300", //填充颜色
stroke: "#5E2300", //笔触颜色
scaleX: 4,//x轴放大倍数
scaleY: 4,//y轴放大倍速
strokeWidth: 4, //笔触宽度
selectable: true, //是否可被选中
transparentCorners: false, //选中后的控制手柄的是否透明
cornerColor: "blue", //选中后的控制手柄颜色
hasControls: true, //选中时是否可以放大缩小
hasRotatingPoint: true, //选中时是否可以旋转
hasBorders: true, //选中时是否有边框
lockMovementX: true, //X轴是否可被移动(true为不可,因为前缀是lock)
lockMovementY: true, //Y轴是否可被移动(true为不可,因为前缀是lock)

更多的属性,可以查看对应文档:JSDoc: Home (fabricjs.com)

五,操作对象

上文我们只是创建了对象,在某个时候,我们可能会想要修改那些对象。

也许某些动作将需要触发状态改变,或者播放某种形式的动画。或者,我们可能想更改某些鼠标交互时的对象属性(颜色,不透明度,大小,位置)。

Fabric和vue一样,数据驱动视图,我们只要变更对应对象的属性值,就可以驱动视图发生相应的变化。

而这些可供修改的属性,都与定位有关——left、top;尺寸——width、height;渲染——fill、opacity、stroke、strokeWidth;缩放和旋转——scaleX、scaleY、angle;甚至与翻转相关的内容——flipX、flipY和倾斜的skewX、skewY。

1,读取对象属性
obj.get('属性名')

例如,获取距离左侧的宽度:

//新建画布对象
    const canvas = new fabric.Canvas("canvas", {
      backgroundColor: "pink",
      selectionColor: "blue",
      selectionLineWidth: 2,
    });
    // 多边形
    const polygon = new fabric.Polygon(
      [
        {
          x: 100,
          y: 10,
        },
        {
          x: 250,
          y: 10,
        },
        {
          x: 250,
          y: 180,
        },
        {
          x: 150,
          y: 180,
        },
      ],
      {
        left: 100,
        top: 50,
        fill: "red",
        strokeWidth: 4,
        stroke: "green",
        perPixelTargetFind: true,
      }
    );
    canvas.add(polygon);
    console.log("获取距离左侧距离", polygon.get("left"));
2,设置对象属性
obj.set("属性名",新的属性值)

这个set还支持链式调用,并且,在canvas.add(polygon);之前或者之后调用,都是可以的:

polygon.set("fill", "blue").set("stroke", "red");//将上文的多边形填充和边框颜色变更

或者obj.set传入属性对象也是可以的

obj.set({属性名:属性值})
3,自定义原型对象属性方法

在fabric中的大多数对象都从根fabric.Object继承。 fabric.Object几乎代表一个二维形状,位于二维画布平面中。它是一个具有left / top和width / height属性以及一系列其他图形特征的实体。我们在对象上看到的那些属性(fill、stroke、angle、opacity、flip *等)对于所有从fabric.Object继承的Fabric对象都是共有的。 这种继承使我们可以在fabric.Object上定义方法,并在所有子“类”之间共享它们。例如,如果要在所有对象上都具有getWidth方法,则只需在fabric.Object.prototype上创建它:

fabric.Object.prototype.getWidth = function () {
      return this.get("width");
};
    //新建画布对象
    const canvas = new fabric.Canvas("canvas", {
      backgroundColor: "pink",
      selectionColor: "blue",
      selectionLineWidth: 2,
    });
    // 多边形
    const polygon = new fabric.Polygon(
      [
        {
          x: 100,
          y: 10,
        },
        {
          x: 250,
          y: 10,
        },
        {
          x: 250,
          y: 180,
        },
        {
          x: 150,
          y: 180,
        },
      ],
      {
        left: 100,
        top: 50,
        fill: "red",
        strokeWidth: 4,
        stroke: "green",
        perPixelTargetFind: true,
      }
    );
    canvas.add(polygon);
    console.log("获取宽度", polygon.getWidth());

六,Image对象

图像对象和基础对象又有所不同,因为它需要开发者额外导入图像资源。

这里需要区分两种情况。

1,document上有已经渲染的图像

这种写法需要等图像已经渲染出来了,才能创建fabric对象。所以我把创建canvas放在image的load方法内部了。

<template>
  <div id="app">
    <!-- 1、创建 canvas 元素 -->
    <canvas
      width="400"
      height="400"
      id="canvas"
      style="border: 1px solid #ccc"
    ></canvas>
    <img
      src="./hq-wmyd.png"
      alt="picture"
      id="img"
      width="100"
      height="auto"
      @load="imageLoad"
    />
  </div>
</template>

<script>
//引入fabric
import { fabric } from "fabric";
export default {
  name: "App",
  components: {},
  mounted() {},
  data() {
    return {};
  },
  methods: {
    imageLoad() {
      const canvas = new fabric.Canvas("canvas");
      canvas.setBackgroundColor("rgb(100,200,200)");
      const imgElement = document.getElementById("img");
      const imgInstance = new fabric.Image(imgElement, {
        left: 100,
        top: 100,
        angle: 45,
        opacity: 0.6,
      });
      console.log(imgInstance);
      canvas.add(imgInstance);
    },
  },
};
</script>

<style scoped lang="scss"></style>
2,采用图片地址的方式
const canvas = new fabric.Canvas("canvas");
canvas.setBackgroundColor("rgb(100,200,200)");
fabric.Image.fromURL(
   "https://i.niupic.com/images/2022/10/15/a9pJ.png",
    (oImg) => {
     canvas.add(oImg);
    }
);

实现的效果均是如下:

fabricjs 如何变形_ci_02

七,路径对象path

上文我们已经能够绘制基本的图案了。

如果想要画出更复杂的图案,则需要引入path。

Fabric中的路径非常类似于SVG <path>元素。它们使用相同的命令集。Fabric中的路径代表可以用其他方式填充,描边和修改的形状轮廓。路径由一系列命令组成,这些命令实际上模仿了笔从一个点到另一个点的过程。借助“移动”,“线”,“曲线”或“弧”等命令,路径可以形成难以置信的复杂形状。

const canvas = new fabric.Canvas("canvas");
    canvas.setBackgroundColor("rgb(100,200,200)");
    const path = new fabric.Path(
      `M8870 5431 c-20 -11 -46 -38 -60 -63 l-25 -43 -5 -1985 c-5 -1969 -5
-1985 15 -2029 36 -80 41 -81 412 -81 l323 0 29 57 c98 194 293 384 499 487
81 41 217 83 326 102 147 25 406 16 538 -19 301 -79 541 -276 674 -553 l45
-94 421 0 421 0 63 31 c88 43 186 135 221 207 l28 57 3 728 3 728 -35 104
c-45 134 -88 218 -199 390 -191 296 -1019 1641 -1061 1723 -76 150 -232 238
-466 262 -64 6 -479 10 -1119 10 -970 -1 -1018 -2 -1051 -19z m1991 -291 c171
-17 279 -58 309 -117 8 -15 62 -107 119 -203 281 -475 476 -823 537 -961 26
-58 39 -103 42 -146 4 -58 2 -64 -25 -92 -16 -16 -47 -34 -69 -40 -55 -15
-2238 -15 -2299 0 -52 13 -119 67 -144 118 -40 79 -43 122 -39 731 3 577 3
586 25 632 13 29 35 55 55 68 32 19 48 20 709 20 393 0 720 -4 780 -10z m1605
-3111 c49 -45 54 -72 54 -294 0 -236 -7 -266 -64 -290 -71 -30 -157 -13 -206
39 -24 26 -25 34 -28 173 -7 312 33 409 166 400 38 -3 59 -10 78 -28z`
    );
    let commonObj = {
      left: 10,
      top: 10,
      fill: "#5E2300",
      scaleX: 0.01,
      scaleY: 0.01,
    };
    canvas.add(path.set(commonObj));

创建路径的流程是:实例化fabric.Path对象,并向其传递一串路径指令。虽然看起来很神秘,但实际上很容易理解。 “ M”表示“移动”命令,并指示隐形笔移动到第一点。 “ L”代表“线”,使笔画一条线到下一点。然后,另一个“ c”创建一条到另一点的贝塞尔曲线。最后,“ z”告诉强制绘图笔关闭路径并最终确定形状。

由于fabric.Path就像Fabric中的任何其他对象一样,我们也能够更改其某些属性。

可以看到,路径是很多的点数据,所以通常我们很少会手动创建Path实例,而是使用Fabric的内置SVG解析器来获取路径。

上面代码实现的效果:

fabricjs 如何变形_fabricjs 如何变形_03

八,文本对象

fabric提供的文本对象支持多行输入、文字对齐方式选择、文字背景设置、文本修饰、文本行高、设置字间距、修改样式、富文本(支持标签)、在 canvas 进行输入编辑。 常用的创建文本的方法如下,第一个参数是文本内容,第二个则是文本的属性对象:

const canvas = new fabric.Canvas("canvas");
    canvas.setBackgroundColor("rgb(100,200,200)");
    const txt = new fabric.Text('test', {
      left: 140, // 位置
      top: 140,
      shadow: 'rgba(0,0,0,0.3) 15px 15px 15px', // 阴影
      fontFamily: 'Hoefler Text', // 字体
      stroke: '#ff1318', // 画笔颜色
      strokeWidth: 2, // 画笔粗度
      fill: '#0f0', //填充颜色
      fontSize: 60, // 字体大小
    });
    canvas.add(txt);

实现的效果:

fabricjs 如何变形_fabric_04

九,组的概念

上文讲的都是单个元素的创建和操作,除此之外,fabric也提供了将创建的对象进行组合的方法 group ,将几个对象进行组合成为一个新的对象之后进行操作:

const canvas = new fabric.Canvas("canvas");
    canvas.setBackgroundColor("rgb(100,200,200)");
    const rect = new fabric.Rect({
        //自己对象的属性可以单独设置
      width: 30,
      height: 30,
      fill: "yellow",
    });
    const ellipse = new fabric.Ellipse({
      rx: 10, //短轴
      ry: 15, //长轴
      fill: "red", //填充颜色
      stroke: "rgba(0, 0, 0, 1)", //边框颜色
    });
    const group = new fabric.Group([ rect, ellipse ], {
        //公用的属性可以设置到这里来
        left: 0,
        top: 0,
    });
    canvas.add(group);

实现的效果如下图:

fabricjs 如何变形_fabricjs 如何变形_05

而如果需要设置对象在组合中的位置,可以在创建对象的时候设置位置:

const rect = new fabric.Rect({
+      left: 15,
+      top: 10,//这里设置位置,是以groups的左上角为基准的相对坐标值
      width: 30,
      height: 30,
      fill: "yellow",
    });