案例+图解带你遨游 Canvas 2D绘图 Fabric.js🔥🔥(5W+字)

Fabric.js 简介

Fabric.js 是一个功能强大且操作简单的 Javascript HTML5 canvas 工具库。

『Fabric.js 官网首页』

『Fabric.js Demos』

本文主要讲解 Fabric.js 有基础也有实战,包括:

  • 画布的基本操作
  • 基础图形绘制方法(矩形、圆形、三角形、椭圆、多边形、线段等)
  • 自定义图形
  • 图片的使用
  • 文本和文本框
  • 图形和文本的基础样式
  • 渐变
  • 选中状态
  • 分组和取消分组
  • 动画
  • 设置和获取图形层级
  • 基础事件
  • 禁止水平、垂直移动
  • 缩放和平移画布
  • 视口坐标和画布坐标转换
  • 序列化和反序列化
  • ......

起步

1. 新建页面并引入 Fabric.js

ts 复制代码
import { fabric } from 'fabric'

2. 创建 canvas 容器

HTML 中创建 <canvas>,并设置容器的 id宽高,width/height

html 复制代码
<canvas width="400" height="400" id="c" style="border: 1px solid #ccc;"></canvas>

这里创建了一个 canvas 容器,边框为灰色,id="c" 。指定长宽都为 400px

3. 使用 fabric 接管容器,并画一个矩形

JS 中实例化 fabric ,之后就可以使用 fabricapi 管理 canvas 了。

html 复制代码
<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric' // 引入 fabric

function init() {
  const canvas = new fabric.Canvas('c') // 这里传入的是canvas的id

  // 创建一个长方形
  const rect = new fabric.Rect({
    top: 30, // 距离容器顶部 30px
    left: 30, // 距离容器左侧 30px
    width: 100, // 宽 100px
    height: 60, // 高 60px
    fill: 'red' // 填充 红色
  })

  // 在canvas画布中加入矩形(rect)。
  canvas.add(rect)
}

onMounted(() => {
  init()
})
</script>

画布

Fabric.js 的画布操作性是非常强的。

基础版(可交互)

基础版就是"起步"章节所说的那个例子。

html 复制代码
<template>
  <canvas width="400" height="400" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas') // 这里传入的是canvas元素的id

  // 创建一个长方形
  const rect = new fabric.Rect({
    top: 100, // 距离容器顶部 100px
    left: 100, // 距离容器左侧 100px
    width: 30, // 矩形宽度 30px
    height: 30, // 矩形高度 30px
    fill: 'red' // 填充 红色
  })

  canvas.add(rect) // 将矩形添加到 canvas 画布里
}

onMounted(() => {
  init()
})
</script>

不可交互

html 复制代码
<template>
  <canvas width="400" height="400" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  // 使用 StaticCanvas 创建一个不可操作的画布
  const canvas = new fabric.StaticCanvas('canvas') // 这里传入的是canvas元素的id

  // 创建一个长方形
  const rect = new fabric.Rect({
    top: 100, // 距离容器顶部 100px
    left: 100, // 距离容器左侧 100px
    width: 30, // 矩形宽度 30px
    height: 30, // 矩形高度 30px
    fill: 'red' // 填充 红色
  })

  canvas.add(rect)
}

onMounted(() => {
  init()
})
</script>

创建不可交互的画布,其实只需把 new fabric.Canvas 改成 new fabric.StaticCanvas 即可。

在js设定画布参数

ts 复制代码
const canvas = new fabric.Canvas('canvas', {
    width: 300, // 画布宽度
    height: 300, // 画布高度
    backgroundColor: '#eee' // 画布背景色
})
</script>

使用背景图

html 复制代码
<template>
  <canvas width="400" height="400" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  // 设置背景图
  // 参数1:背景图资源(可以引入本地,也可以使用网络图)
  // 参数2:设置完背景图执行以下重新渲染canvas的操作,这样背景图就会展示出来了
  canvas.setBackgroundImage(
    '图片url',
    canvas.renderAll.bind(canvas)
  )
}

onMounted(() => {
  init()
})
</script>

拉伸背景图

html 复制代码
<template>
  <canvas width="400" height="400" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  // fabric.Image.fromURL:加载图片的api
  // 第一个参数:图片地址(可以是本地的,也可以是网络图)
  // 第二个参数:图片加载的回调函数
  fabric.Image.fromURL(
    '图片url',
    (img) => {
      // 设置背景图
      canvas.setBackgroundImage(
        img,
        canvas.renderAll.bind(canvas),
        {
          scaleX: canvas.width / img.width, // 计算出图片要拉伸的宽度
          scaleY: canvas.height / img.height // 计算出图片要拉伸的高度
        }
      )
    }
  )
}

onMounted(() => {
  init()
})
</script>

这个例子使用了 fabric.Image.fromURL 这个 api 来加载图片,第一个参数是图片地址,第二个参数是回调函数。

拿到图片的参数和画布的宽高进行计算,从而使图片充满全屏。

基础图形

矩形

html 复制代码
<template>
  <canvas width="400" height="375" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const rect = new fabric.Rect({
    top: 100, // 距离容器顶部 100px
    left: 100, // 距离容器左侧 100px
    fill: 'orange', // 填充 橙色
    width: 100, // 宽度 100px
    height: 100 // 高度 100px
  })
  
  // 将矩形添加到画布中
  canvas.add(rect)
}

onMounted(() => {
  init()
})
</script>

使用 new fabric.Rect 创建 矩形

圆角矩形

html 复制代码
<template>
  <canvas width="400" height="375" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const rect = new fabric.Rect({
    top: 100, // 距离容器顶部 100px
    left: 100, // 距离容器左侧 100px
    fill: 'orange', // 填充 橙色
    width: 100, // 宽度 100px
    height: 100, // 高度 100px
    rx: 20, // x轴的半径
    ry: 20 // y轴的半径
  })
  
  // 将矩形添加到画布中
  canvas.add(rect)
}

onMounted(() => {
  init()
})
</script>

画圆角矩形,需要添加 rxry

圆形

html 复制代码
<template>
  <canvas width="400" height="375" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const circle = new fabric.Circle({
    top: 100,
    left: 100,
    radius: 50, // 圆的半径 50
    fill: 'green'
  })
  canvas.add(circle)
}

onMounted(() => {
  init()
})
</script>

使用 new fabric.Circle 创建圆形

圆形需要使用 radius 设置半径大小。

椭圆形

html 复制代码
<template>
  <canvas width="400" height="375" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const ellipse = new fabric.Ellipse({
    top: 20,
    left: 20,
    rx: 70,
    ry: 30,
    fill: 'hotpink'
  })
  canvas.add(ellipse)
}

onMounted(() => {
  init()
})
</script>

需要使用 new fabric.Ellipse 创建 椭圆

和圆形不同,椭圆不需要设置 radius ,但要设置 rxry

  • rx > ry :椭圆是横着的
  • rx < ry:椭圆是竖着的
  • rx = ry: 看上去就是个圆形

三角形

html 复制代码
<template>
  <canvas width="400" height="375" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const triangle = new fabric.Triangle({
    top: 100,
    left: 100,
    width: 80, // 底边长度
    height: 100, // 底边到对角的距离
    fill: 'blue'
  })
  canvas.add(triangle)
}

onMounted(() => {
  init()
})
</script>

使用 new fabric.Triangle 创建三角形,三角形是需要给定 "底和高" 的。

线段

html 复制代码
<template>
  <canvas width="400" height="375" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const line = new fabric.Line(
    [
      10, 10, // 起始点坐标
      200, 300 // 结束点坐标
    ],
    {
      stroke: 'red', // 笔触颜色
    }
  )
  canvas.add(line)
}

onMounted(() => {
  init()
})
</script>

使用 new fabric.Line 创建线段。

new fabric.Line 需要传入2个参数。

  • 第一个参数是 数组 ,数组需要传4个值,前2个值是起始坐标的x和y,后2个值是结束坐标的x和y

  • 第二个参数是 线段的样式 ,要设置线段的颜色,需要使用 stroke

折线

html 复制代码
<template>
  <canvas width="400" height="375" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const polyline = new fabric.Polyline([
    {x: 30, y: 30},
    {x: 150, y: 140},
    {x: 240, y: 150},
    {x: 100, y: 30}
  ], {
    fill: 'transparent', // 如果画折线,需要填充透明
    stroke: '#6639a6', // 线段颜色:紫色
    strokeWidth: 5 // 线段粗细 5
  })
  canvas.add(polyline)
}

onMounted(() => {
  init()
})
</script>

使用 new fabric.Polyline 创建线段

new fabric.Polyline 需要传入2个参数。

  • 第一个参数是数组,描述线段的每一个点
  • 第二个参数用来描述线段样式

需要注意的是, fill 设置成透明才会显示成线段,如果不设置,会默认填充黑色,如下图所示:

你也可以填充自己喜欢的颜色,new fabric.Polyline 是不会自动把 起始点结束点 自动闭合起来的。

多边形

html 复制代码
<template>
  <canvas width="400" height="375" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const polygon = new fabric.Polygon([
    {x: 30, y: 30},
    {x: 150, y: 140},
    {x: 240, y: 150},
    {x: 100, y: 30}
  ], {
    fill: '#ffd3b6', // 填充色
    stroke: '#6639a6', // 线段颜色:紫色
    strokeWidth: 5 // 线段粗细 5
  })
  canvas.add(polygon)
}

onMounted(() => {
  init()
})
</script>

使用 new fabric.Polygon 绘制多边形,用法和 new fabric.Polyline 差不多,但最大的不同点是 new fabric.Polygon 会自动把 起始点结束点 连接起来。

自定义图形

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

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

一个自定义子类的结构:

ts 复制代码
      // 创建一个自定义子类
      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: 处理自定义渲染逻辑

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

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

ts 复制代码
      // 绘制表格图形
      function initTable(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();
      }

创建 Table 子类:

ts 复制代码
      // 创建一个自定义子类
      const Map = fabric.util.createClass(fabric.Object, {
        type: "Table",
        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);
          initTable({
            ...this
          }, ctx)
        },
      });

新建 Table 实例并添加到canvas:

ts 复制代码
      const table = new Table({
        left: 100,
        top: 100,
        label: "test",
        fill: "#faa",
        width: 100,
        height: 100,
        gridNumX: 4,
        gridNumY: 3
      });

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

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

文本

Fabric.js 有3类跟文本相关的 api

  • 普通文本
  • 可编辑文本
  • 文本框

普通文本 Text

html 复制代码
<template>
  <canvas width="400" height="400" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const text = new fabric.Text('hello')
  canvas.add(text)
}

onMounted(() => {
  init()
})
</script>

使用 new fabric.Text 创建文本,传入第一个参数就是文本内容。

new fabric.Text 还支持第二个参数,可以设置文本样式。

可编辑文本 IText

html 复制代码
<template>
  <canvas width="400" height="400" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const itext = new fabric.IText('hello')
  canvas.add(itext)
}

onMounted(() => {
  init()
})
</script>

使用 new fabric.IText 可以创建可编辑文本 ,用法和 new fabric.Text 一样。

文本框 Textbox

html 复制代码
<template>
  <canvas width="400" height="400" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const textbox = new fabric.Textbox('What are you doing?', {
    width: 250
  })
  canvas.add(textbox)
}

onMounted(() => {
  init()
})
</script>

使用 new fabric.Textbox 可以创建文本框。

new fabric.Textbox 第二个参数是对象,使用 width 可以设定了文本框的宽度,文本内容超过设定的宽度会自动换行。

new fabric.Textbox 的内容同样是可编辑的。

基础样式

图形常用样式

html 复制代码
<template>
  <canvas width="400" height="400" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const circle = new fabric.Circle({
    top: 100,
    left: 100,
    radius: 50, // 半径:50px
    backgroundColor: 'green', // 背景色:绿色
    fill: 'orange', // 填充色:橙色
    stroke: '#f6416c', // 边框颜色:粉色
    strokeWidth: 5, // 边框粗细:5px
    strokeDashArray: [20, 5, 14], // 边框虚线规则:填充20px 空5px 填充14px 空20px 填充5px ......
    shadow: '10px 20px 6px rgba(10, 20, 30, 0.4)', // 投影:向右偏移10px,向下偏移20px,羽化6px,投影颜色及透明度
    transparentCorners: false, // 选中时,角是被填充了。true 空心;false 实心
    borderColor: '#16f1fc', // 选中时,边框颜色:天蓝
    borderScaleFactor: 5, // 选中时,边的粗细:5px
    borderDashArray: [20, 5, 10, 7], // 选中时,虚线边的规则
    cornerColor: "#a1de93", // 选中时,角的颜色是 青色
    cornerStrokeColor: 'pink', // 选中时,角的边框的颜色是 粉色
    cornerStyle: 'circle', // 选中时,叫的属性。默认rect 矩形;circle 圆形
    cornerSize: 20, // 选中时,角的大小为20
    cornerDashArray: [10, 2, 6], // 选中时,虚线角的规则
    selectionBackgroundColor: '#7f1300', // 选中时,选框的背景色:朱红
    padding: 40, // 选中时,选择框离元素的内边距:40px
    borderOpacityWhenMoving: 0.6, // 当对象活动和移动时,对象控制边界的不透明度  
  })

  canvas.add(circle)
}

onMounted(() => {
  init()
})
</script>

文本常用样式

html 复制代码
<template>
  <canvas width="400" height="400" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const text = new fabric.Text('hello', {
    top: 40,
    left: 40,
    fontSize: 120,
    backgroundColor: 'green', // 背景色:绿色
    fill: 'orange', // 填充色:橙色
    stroke: '#f6416c', // 边框颜色:粉色
    strokeWidth: 3, // 边框粗细:3px
    strokeDashArray: [20, 5, 14], // 边框虚线规则:填充20px 空5px 填充14px 空20px 填充5px ......
    shadow: '10px 20px 6px rgba(10, 20, 30, 0.4)', // 投影:向右偏移10px,向下偏移20px,羽化6px,投影颜色及透明度
    transparentCorners: false, // 选中时,角是被填充了。true 空心;false 实心
    borderColor: '#16f1fc', // 选中时,边框颜色:天蓝
    borderScaleFactor: 5, // 选中时,边的粗细:5px
    borderDashArray: [20, 5, 10, 7], // 选中时,虚线边的规则
    cornerColor: "#a1de93", // 选中时,角的颜色是 青色
    cornerStrokeColor: 'pink', // 选中时,角的边框的颜色是 粉色
    cornerStyle: 'circle', // 选中时,叫的属性。默认rect 矩形;circle 圆形
    cornerSize: 20, // 选中时,角的大小为20
    cornerDashArray: [10, 2, 6], // 选中时,虚线角的规则
    selectionBackgroundColor: '#7f1300', // 选中时,选框的背景色:朱红
    padding: 40, // 选中时,选择框离元素的内边距:40px
    borderOpacityWhenMoving: 0.6, // 当对象活动和移动时,对象控制边界的不透明度  
  })

  canvas.add(text)
}

onMounted(() => {
  init()
})
</script>

除此之外,还可以配置 上划线下划线删除线左对齐右对齐居中对齐行距 等。

html 复制代码
<template>
  <canvas width="600" height="400" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  // 上划线
  const overline = new fabric.Text('上划线', {
    top: 30,
    left: 10,
    fontSize: 20,
    overline: true, // 上划线
  })

  // 下划线
  const underline = new fabric.Text('下划线', {
    top: 30,
    left: 100,
    fontSize: 20,
    underline: true, // 下划线
  })

  // 删除线
  const linethrough = new fabric.Text('删除线', {
    top: 30,
    left: 200,
    fontSize: 20,
    linethrough: true, // 删除线
  })

  // 左对齐
  const msg1 = '左\n左左\n左对齐'
  const left = new fabric.Text(msg1, {
    top: 100,
    left: 10,
    fontSize: 16,
    textAlign: 'left', // 左对齐
  })

  // 居中对齐
  const msg2 = '中\n中中\n居中对齐'
  const center = new fabric.Text(msg2, {
    top: 100,
    left: 100,
    fontSize: 16,
    textAlign: 'center',// 居中对齐
  })

  // 右对齐
  const msg3 = '右\n右右\n右对齐'
  const right = new fabric.Text(msg3, {
    top: 100,
    left: 200,
    fontSize: 16,
    textAlign: 'right', // 右对齐
  })

  // 文本内容
  const msg4 = "What are you doing,\nWhat are you doing,\nWhat are you doing\What are you doing"
  
  const lineHeight1 = new fabric.Text(msg4, {
    top: 250,
    left: 10,
    fontSize: 16,
    lineHeight: 1, // 行高
  })

  const lineHeight2 = new fabric.Text(msg4, {
    top: 250,
    left: 300,
    fontSize: 16,
    lineHeight: 2, // 行高
  })

  canvas.add(
    overline,
    underline,
    linethrough,
    left,
    center,
    right,
    lineHeight1,
    lineHeight2
  )

}

onMounted(() => {
  init()
})
</script>

渐变

线性渐变

html 复制代码
<template>
  <canvas width="600" height="600" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  let canvas = new fabric.Canvas('canvas')

  // 圆
  let circle = new fabric.Circle({
    left: 100,
    top: 100,
    radius: 50,
  })

  // 线性渐变
  let gradient = new fabric.Gradient({
    type: 'linear', // linear or radial
    gradientUnits: 'pixels', // pixels or pencentage 像素 或者 百分比
    coords: { x1: 0, y1: 0, x2: circle.width, y2: 0 }, // 至少2个坐标对(x1,y1和x2,y2)将定义渐变在对象上的扩展方式
    colorStops:[ // 定义渐变颜色的数组
      { offset: 0, color: 'red' },
      { offset: 0.2, color: 'orange' },
      { offset: 0.4, color: 'yellow' },
      { offset: 0.6, color: 'green' },
      { offset: 0.8, color: 'blue' },
      { offset: 1, color: 'purple' },
    ]
  })
  circle.set('fill', gradient);
  canvas.add(circle)
}

onMounted(() => {
  init()
})
</script>

径向渐变

html 复制代码
<template>
  <canvas width="600" height="600" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  let canvas = new fabric.Canvas('canvas')

  // 圆
  let circle = new fabric.Circle({
    left: 100,
    top: 100,
    radius: 50,
  })

  let gradient = new fabric.Gradient({
    type: 'radial',
    coords: {
      r1: 50, // 该属性仅径向渐变可用,外圆半径
      r2: 0, // 该属性仅径向渐变可用,外圆半径  
      x1: 50, // 焦点的x坐标
      y1: 50, // 焦点的y坐标
      x2: 50, // 中心点的x坐标
      y2: 50, // 中心点的y坐标
    },
    colorStops: [
      { offset: 0, color: '#fee140' },
      { offset: 1, color: '#fa709a' }
    ]
  })

  circle.set('fill', gradient);
  canvas.add(circle)
}

onMounted(() => {
  init()
})
</script>

选中状态

Fabric.js 创建出来的元素(图形、图片、组等)默认是可以被选中的。

禁止选中

html 复制代码
<template>
  <canvas width="400" height="400" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  const rect = new fabric.Rect({
    top: 100,
    left: 100,
    width: 200,
    height: 100,
    fill: 'red'
  })

  // 元素禁止选中
  rect.selectable = false

  canvas.add(rect)
}

onMounted(() => {
  init()
})
</script>

框选样式

html 复制代码
<template>
  <canvas width="200" height="200" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  // 圆形
  const circle = new fabric.Circle({
    radius: 30,
    fill: '#f55',
    top: 70,
    left: 70
  })

  canvas.add(circle)

  canvas.selection = true // 画布是否可选中。默认true;false 不可选中
  canvas.selectionColor = 'rgba(106, 101, 216, 0.3)' // 画布鼠标框选时的背景色
  canvas.selectionBorderColor = "#1d2786" // 画布鼠标框选时的边框颜色
  canvas.selectionLineWidth = 6 // 画布鼠标框选时的边框厚度
  canvas.selectionDashArray = [30, 4, 10] // 画布鼠标框选时边框虚线规则
  canvas.selectionFullyContained = true // 只选择完全包含在拖动选择矩形中的形状
}

onMounted(() => {
  init()
})
</script>

自定义边和控制角样式

html 复制代码
<template>
  <canvas width="200" height="200" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  // 圆形
  const circle = new fabric.Circle({
    radius: 30,
    fill: '#f55',
    top: 70,
    left: 70
  })

  circle.set({
    borderColor: 'red', // 边框颜色
    cornerColor: 'green', // 控制角颜色
    cornerSize: 10, // 控制角大小
    transparentCorners: false // 控制角填充色不透明
  })

  canvas.add(circle)

  canvas.setActiveObject(circle) // 选中圆
}

onMounted(() => {
  init()
})
</script>

没有控制角

没有控制角将意味着无法用鼠标直接操作缩放和旋转,只允许移动操作。

html 复制代码
<template>
  <canvas width="200" height="200" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  // 圆形
  const circle = new fabric.Circle({
    radius: 30,
    fill: '#f55',
    top: 70,
    left: 70
  })

  circle.hasControls = false // 禁止控制角

  canvas.add(circle)

  canvas.setActiveObject(circle) // 选中第一项
}

onMounted(() => {
  init()
})
</script>

不允许框选

不允许从画布框选,但允许选中元素。

html 复制代码
<template>
  <canvas width="200" height="200" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  // 圆形
  const circle = new fabric.Circle({
    radius: 30,
    fill: '#f55',
    top: 70,
    left: 70
  })

  canvas.add(circle)
  canvas.selection = false // 不允许直接从画布框选
}

onMounted(() => {
  init()
})
</script>

分组

建组

html 复制代码
<template>
  <div>
    <canvas width="400" height="400" id="canvas" style="border: 1px solid #ccc;"></canvas>
  </div>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  // 椭圆
  const ellipse = new fabric.Ellipse({
    top: 20,
    left: 20,
    rx: 100,
    ry: 50,
    fill: '#ddd',
    originX: 'center', // 旋转x轴:left, right, center
    originY: 'center' // 旋转y轴:top, bottom, center
  })

  // 文本
  const text = new fabric.Text('Hello World', {
    top: 40,
    left: 20,
    fontSize: 20,
    originX: "center",
    originY: "center"
  })

  // 建组
  const group = new fabric.Group([ellipse, text], {
    top: 50, // 整组距离顶部100
    left: 100, // 整组距离左侧100
    angle: -10, // 整组旋转-10deg
  })

  canvas.add(group)
}

onMounted(() => {
  init()
})
</script>

new fabric.Group 可以创建一个组,把多个图层放在同一个组内,实现同步的操作,比如拖拽、缩放等。

操作组

Fabric.js 的组提供了很多方法,这里列一些常用的:

  • getObjects() 返回一组中所有对象的数组

  • size() 所有对象的数量

  • contains() 检查特定对象是否在 group

  • item() 组中元素

  • forEachObject() 遍历组中对象

  • add() 添加元素对象

  • remove() 删除元素对象

  • fabric.util.object.clone() 克隆

html 复制代码
<template>
  <div>
    <canvas width="400" height="400" id="canvas" style="border: 1px solid #ccc;"></canvas>
  </div>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const canvas = new fabric.Canvas('canvas')

  // 椭圆
  const ellipse = new fabric.Ellipse({
    top: 20,
    left: 20,
    rx: 100,
    ry: 50,
    fill: '#ddd',
    originX: 'center', // 旋转x轴:left, right, center
    originY: 'center' // 旋转y轴:top, bottom, center
  })

  // 文本
  const text = new fabric.Text('Hello World', {
    top: 40,
    left: 20,
    fontSize: 20,
    originX: "center",
    originY: "center"
  })

  // 建组
  const group = new fabric.Group([ellipse, text], {
    top: 50, // 整组距离顶部100
    left: 100, // 整组距离左侧100
    angle: -10, // 整组旋转-10deg
  })

  // 控制第一个元素(椭圆)
  group.item(0).set('fill', '#ea5455')

  // 控制第二个元素(文本)
  group.item(1).set({
    text: '雷猴,世界',
    fill: '#fff'
  })

  canvas.add(group)
}

onMounted(() => {
  init()
})
</script>

取消分组

html 复制代码
<template>
  <div>
    <button @click="ungroup">取消组</button>
    <canvas width="400" height="400" id="canvas" style="border: 1px solid #ccc;"></canvas>
  </div>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

let canvas = null

// 初始化
function init() {
  canvas = new fabric.Canvas('canvas')

  // 椭圆
  const ellipse = new fabric.Ellipse({
    top: 20,
    left: 20,
    rx: 100,
    ry: 50,
    fill: '#ddd',
    originX: 'center', // 旋转x轴:left, right, center
    originY: 'center' // 旋转y轴:top, bottom, center
  })

  // 文本
  const text = new fabric.Text('Hello World', {
    top: 40,
    left: 20,
    fontSize: 20,
    originX: "center",
    originY: "center"
  })

  // 建组
  const group = new fabric.Group([ellipse, text], {
    top: 50, // 整组距离顶部100
    left: 100, // 整组距离左侧100
    angle: -10, // 整组旋转-10deg
  })

  canvas.add(group)
}

// 取消组
function ungroup() {
  // 判断当前有没有选中元素,如果没有就不执行任何操作
  if (!canvas.getActiveObject()) {
    return
  }

  // 判断当前是否选中组,如果不是,就不执行任何操作
  if (canvas.getActiveObject().type !== 'group') {
    return
  }

  // 先获取当前选中的对象,然后打散
  canvas.getActiveObject().toActiveSelection()
}

onMounted(() => {
  init()
})
</script>

动画

绝对值动画

html 复制代码
<template>
  <div>
    <canvas width="400" height="400" id="canvas" style="border: 1px solid #ccc;"></canvas>
  </div>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

// 初始化
function init() {
  const canvas = new fabric.Canvas('canvas')

  const rect = new fabric.Rect({
    left: 100,
    top: 100,
    width: 100,
    height: 100,
    fill: 'red'
  })

  // 设置矩形动画
  rect.animate('angle', "-50", {
    onChange:canvas.renderAll.bind(canvas), // 每次刷新的时候都会执行
  })

  canvas.add(rect)
}

onMounted(() => {
  init()
})
</script>

每个 Fabric 对象都有一个 animate 方法,该方法可以动画化该对象。

用法:animate(动画属性, 动画的结束值, [画的详细信息])

第一个参数是要设置动画的属性。

第二个参数是动画的结束值。

第三个参数是一个对象,包括:

ts 复制代码
{

   rom:允许指定可设置动画的属性的起始值(如果我们不希望使用当前值)。

   duration:默认为500(ms)。可用于更改动画的持续时间。

   onComplete:在动画结束时调用的回调。

   easing:缓动功能。

}

相对值动画

html 复制代码
<template>
  <div>
    <canvas width="400" height="400" id="canvas" style="border: 1px solid #ccc;"></canvas>
  </div>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

// 初始化
function init() {
  const canvas = new fabric.Canvas('canvas')
  const rect = new fabric.Rect({
    left: 100,
    top: 100,
    width: 100,
    height: 100,
    fill: 'red'
  })

  // 请注意第二个参数:+=360
  rect.animate('angle', '+=360', {
    onChange:canvas.renderAll.bind(canvas), // 每次刷新的时候都会执行
    duration: 2000, // 执行时间
    easing: fabric.util.ease.easeOutBounce, // 缓冲效果
  })

  canvas.add(rect)
}

onMounted(() => {
  init()
})
</script>

绝对值动画相对值动画 的用法是差不多的,只是 第二个参数 用法不同。

相对值动画 是把 animate 改成带上运算符的值,这样就会在原基础上做计算。

设置图形层级

  • 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 方法让其直接显示对象的层级。

ts 复制代码
// 新增 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

事件

Fabric.js 提供了一套很方便的事件系统,可以用 on 方法可以初始化事件监听器,用 off 方法将其删除。

html 复制代码
<template>
  <div>
    <canvas width="400" height="400" id="canvas" style="border: 1px solid #ccc;"></canvas>
    <button @click="addClickEvent">添加画布点击事件</button>
    <button @click="removeClickEvent">移除画布点击事件</button>
  </div>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

let canvas = null

// 初始化画布
function init() {
  canvas = new fabric.Canvas('canvas')

  const rect = new fabric.Rect({
    top: 20,
    left: 20,
    width: 100,
    height: 50,
    fill: '#9896f1'
  })

  // 给矩形添加一个选中事件
  rect.on('selected', options => {
    console.log('选中矩形啦', options)
  })
  canvas.add(rect)

  addClickEvent()
}

// 移除画布点击事件
function removeClickEvent() {
  canvas.off('mouse:down')
}

// 添加画布点击事件
function addClickEvent() {
  removeClickEvent() // 在添加事件之前先把该事件清除掉,以免重复添加
  canvas.on('mouse:down', options => {
    console.log(`x轴坐标: ${options.e.clientX};    y轴坐标: ${options.e.clientY}`)
  })
}

onMounted(() => {
  init()
})
</script>

禁止操作框的部分功能

禁止水平移动

html 复制代码
<template>
  <div>
    <canvas id="canvas" width="400" height="400" style="border: 1px solid #ccc;"></canvas>
  </div>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

// 初始化画布
function init() {
  const canvas = new fabric.Canvas('canvas')

  const rect = new fabric.Rect({
    top: 100,
    left: 100,
    width: 100,
    height: 50,
    fill: '#ffde7d'
  })

  // 不允许水平移动
  rect.lockMovementX = true

  canvas.add(rect)
}

onMounted(() => {
  init()
})
</script>

禁止垂直移动

html 复制代码
<template>
  <div>
    <canvas id="canvas" width="400" height="400" style="border: 1px solid #ccc;"></canvas>
  </div>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

// 初始化画布
function init() {
  const canvas = new fabric.Canvas('canvas')

  const rect = new fabric.Rect({
    top: 100,
    left: 100,
    width: 100,
    height: 50,
    fill: '#f6416c'
  })

  // 不允许垂直移动
  rect.lockMovementY = true

  canvas.add(rect)
}

onMounted(() => {
  init()
})
</script>

缩放和平移画布

缩放画布

以原点为基准缩放画布

需要监听鼠标的滚轮事件:mouse:wheel

html 复制代码
<template>
  <div>
    <canvas id="canvas" width="400" height="400" style="border: 1px solid #ccc;"></canvas>
  </div>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  // 初始化画布
  const canvas = new fabric.Canvas('canvas')

  // 矩形
  const rect = new fabric.Rect({
    top: 10,
    left: 10,
    width: 40,
    height: 40,
    fill: 'orange'
  })

  // 圆形
  const circle = new fabric.Circle({
    top: 30,
    left: 30,
    radius: 50,
    fill: 'green'
  })
  canvas.add(rect, circle) // 将矩形和圆形添加到画布中

  // 监听鼠标滚轮事件
  canvas.on('mouse:wheel', opt => {
    let delta = opt.e.deltaY // 滚轮向上滚一下是 -100,向下滚一下是 100
    let zoom = canvas.getZoom() // 获取画布当前缩放值

    // 控制缩放范围在 0.01~20 的区间内
    zoom *= 0.999 ** delta
    if (zoom > 20) zoom = 20
    if (zoom < 0.01) zoom = 0.01

    // 设置画布缩放比例
    canvas.setZoom(zoom)
  })
}

onMounted(() => {
  init()
})
</script>

以鼠标指针为基准缩放画布

html 复制代码
<template>
  <div>
    <canvas id="canvas" width="400" height="400" style="border: 1px solid #ccc;"></canvas>
  </div>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  // 初始化画布
  const canvas = new fabric.Canvas('canvas')

  // 矩形
  const rect = new fabric.Rect({
    top: 130,
    left: 130,
    width: 40,
    height: 40,
    fill: 'orange'
  })

  // 圆形
  const circle = new fabric.Circle({
    top: 150,
    left: 150,
    radius: 50,
    fill: 'green'
  })
  canvas.add(rect, circle) // 将矩形和圆形添加到画布中

  // 监听鼠标滚轮事件
  canvas.on('mouse:wheel', opt => {
    let delta = opt.e.deltaY // 滚轮向上滚一下是 -100,向下滚一下是 100
    let zoom = canvas.getZoom() // 获取画布当前缩放值

    // 控制缩放范围在 0.01~20 的区间内
    zoom *= 0.999 ** delta
    if (zoom > 20) zoom = 20
    if (zoom < 0.01) zoom = 0.01

    // 设置画布缩放比例
    // 关键点!!!
    // 参数1:将画布的所放点设置成鼠标当前位置
    // 参数2:传入缩放值
    canvas.zoomToPoint(
      {
        x: opt.e.offsetX, // 鼠标x轴坐标
        y: opt.e.offsetY  // 鼠标y轴坐标
      },
      zoom // 最后要缩放的值
    )
  })
}

onMounted(() => {
  init()
})
</script>

平移画布

html 复制代码
<template>
  <div>
    <canvas id="canvas" width="400" height="400" style="border: 1px solid #ccc;"></canvas>
  </div>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  // 初始化画布
  const canvas = new fabric.Canvas('canvas')

  // 矩形
  const rect = new fabric.Rect({
    top: 130,
    left: 130,
    width: 40,
    height: 40,
    fill: 'orange'
  })

  // 圆形
  const circle = new fabric.Circle({
    top: 150,
    left: 150,
    radius: 50,
    fill: 'green'
  })
  canvas.add(rect, circle) // 将矩形和圆形添加到画布中

  canvas.on('mouse:down', opt => { // 鼠标按下时触发
    let evt = opt.e
    if (evt.altKey === true) { // 是否按住alt
      canvas.isDragging = true // isDragging 是自定义的,开启移动状态
      canvas.lastPosX = evt.clientX // lastPosX 是自定义的
      canvas.lastPosY = evt.clientY // lastPosY 是自定义的
    }
  })

  canvas.on('mouse:move', opt => { // 鼠标移动时触发
    if (canvas.isDragging) {
      let evt = opt.e
      let vpt = canvas.viewportTransform // 聚焦视图的转换
      vpt[4] += evt.clientX - canvas.lastPosX
      vpt[5] += evt.clientY - canvas.lastPosY
      canvas.requestRenderAll() // 重新渲染
      canvas.lastPosX  = evt.clientX
      canvas.lastPosY  = evt.clientY
    }
  })

  canvas.on('mouse:up', opt => { // 鼠标松开时触发
    canvas.setViewportTransform(canvas.viewportTransform) // 设置此画布实例的视口转换  
    canvas.isDragging = false // 关闭移动状态
  })
}

onMounted(() => {
  init()
})
</script>

获取真实的转换坐标

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

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

ts 复制代码
      // 当鼠标按下时
      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) :

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

修改代码:

ts 复制代码
      // 当鼠标按下时
      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 反转变换。

序列化

输出JSON

html 复制代码
<template>
  <div>
    <canvas id="canvas" width="200" height="200" style="border: 1px solid #ccc;"></canvas>
  </div>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  // 初始化画布
  const canvas = new fabric.Canvas('canvas')

  console.log('canvas stringify ', JSON.stringify(canvas))
  console.log('canvas toJSON', canvas.toJSON())
  console.log('canvas toObject', canvas.toObject())
}

onMounted(() => {
  init()
})
</script>

打开控制台可以看到输出。

输出base64

html 复制代码
<template>
  <div>
    <canvas id="canvas" width="200" height="200" style="border: 1px solid #ccc;"></canvas>
  </div>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  // 初始化画布
  const canvas = new fabric.Canvas('canvas', {
    backgroundColor: '#a5dee5'
  })

  const rect = new fabric.Rect({
    left: 50,
    top: 50,
    height: 20,
    width: 20,
    fill: 'green'
  })

  const circle = new fabric.Circle({
    left: 80,
    top: 80,
    radius: 40,
    fill: "red"
  })

  canvas.add(rect, circle)

  console.log('toPng', canvas.toDataURL('png')) // 在控制台输出 png(base64)
  canvas.requestRenderAll()
}

onMounted(() => {
  init()
})
</script>

输出 SVG

html 复制代码
<template>
  <div>
    <canvas id="canvas" width="200" height="200" style="border: 1px solid #ccc;"></canvas>
  </div>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  // 初始化画布
  const canvas = new fabric.Canvas('canvas', {
    backgroundColor: '#a5dee5'
  })

  const rect = new fabric.Rect({
    left: 50,
    top: 50,
    height: 20,
    width: 20,
    fill: 'green'
  })

  const circle = new fabric.Circle({
    left: 80,
    top: 80,
    radius: 40,
    fill: "red"
  })

  canvas.add(rect, circle)

  console.log(canvas.toSVG()) // 输出 SVG
}

onMounted(() => {
  init()
})
</script>

反序列化

反序列化就是把 JSON 数据渲染到画布上。

通常把从后台请求回来的数据渲染到画布上。

html 复制代码
<template>
  <div>
    <canvas id="canvas" width="200" height="200" style="border: 1px solid #ccc;"></canvas>
  </div>
</template>

<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'

function init() {
  const jsonStr = ''

  // 初始化画布
  const canvas = new fabric.Canvas('canvas')

  // 反序列化
  canvas.loadFromJSON(jsonStr)
}

onMounted(() => {
  init()
})
</script>

总结

写到这里,我们已经把 Fabric.js 常见功能都过了一遍。如果你坚持看到这里,恭喜你!你已经具备构建一个简单画板/海报编辑器/可视化工具的基础能力。

Fabric.js 是一个非常强大的前端 Canvas 库,随着你不断实践,你会发现它能做的事情远不止本文展示的这些。

如果你有什么想法、问题或希望我继续写的方向,欢迎在评论区告诉我,我会持续更新更多相关内容。

相关推荐
2501_944521002 小时前
rn_for_openharmony商城项目app实战-主题设置实现
javascript·数据库·react native·react.js·ecmascript
m0_471199632 小时前
【场景】如何快速接手一个前端项目
前端·vue.js·react.js
榴莲CC2 小时前
抗干扰LED数显屏驱动VK1624 数码管显示芯片 3线串行接口
前端
lili-felicity2 小时前
React Native for Harmony 个人消息列表最新消息置顶实现(多维度权重统计)
javascript·react native·react.js
Tigger2 小时前
用 Vue 3 做了一套年会抽奖工具,顺便踩了些坑
前端·javascript·vue.js
天天扭码2 小时前
一文搞懂——React 19到底更新了什么
前端·react.js·前端框架
weixin_462446233 小时前
【原创】使用langchain与MCP 与 Chrome DevTools 打造可调用浏览器工具的 Chat Agent
前端·langchain·chrome devtools
OpenTiny社区3 小时前
OpenTiny 2025年度贡献者榜单正式公布~
前端·javascript·vue.js
OEC小胖胖3 小时前
08|Commit 阶段:副作用如何被组织、执行与约束
前端·react.js·前端框架·react·开源库