首先要说的是:本文篇幅很
Fabric.js
是一个功能强大且操作简单的Javascript HTML5 canvas
工具库。
如果你需要用 canvas
做特效,那我推荐你使用 Fabric.js
,因为 Fabric.js
语法更加简单易用,而且还提供了很多交互类的 api
。
Fabric.js
简化了很多 Canvas
里的概念,代码看上去也更加语义化。
Fabric.js
能做什么?
可以打开 『Fabric.js 官网首页』 直接看例子,也可以看看 『Fabric.js Demos』 查看更炫酷的例子。
如果是 0基础 的读者,希望可以从头读到尾,读完起码大概知道 Fabric.js
有哪些功能。
本文是根据我学习过程来编写的,只要跟着本文一步一步操作,一定可以入门 Fabric.js
,同时还能改善您的睡眠质量、解决毛发过多等诸多问题。
由于我使用 Fabric.js
的时间不长,这份笔记在各个知识点的内容肯定不够全面的,也不一定完全正确。读者们如果发现本文存在不正确的地方请大胆指出,我会改的~
本文适合人群:
canvas
基础(这是加分项,完全没有也没关系的)本文主要讲解 Fabric.js
基础,包括:
除此之外,还会讲一些进阶一点的操作,比如:
除了上述内容外,我还会根据日后的工作中整理出更多常用和好玩的操作,本文即学习仓库会不定期更新!!!
Fabric.js
的版本是 4.6
。
Vite
构建的 Vue 3.2
项目。
没有 Vite
和 Vue3.2
基础的同学也不用怕,因为 Vite
真的足够简单。
本文的目的是讲解 Fabric.js
,所以用到 Vue 3.2
的地方其实很少,用到时我也会有详细说明。
如果你不打算使用 Vite
和 Vue 3.2
也没关系,用你喜欢的方式去搭建项目即可。
现在只需跟着以下步骤搭建项目即可。
如果你不想使用 Vite + Vue3
的话,可以跳过本节。
我也建议你直接使用原生 (HTML+CSS+JS) 的方式直接学习 Fabric.js
,因为这样上手速度最快。
npm init @vitejs/app
之后会让你选 vue
或者 vue + ts
,我选择了 vue
,你随意。
为什么不选 ts
?因为一人开发的练手项目使用 ts
有点得不偿失。
其实做完上一步就会给出提示(3条命令),跟着敲完就能运行项目了
# 进入项目目录
cd fabric-demo
# 初始化项目
npm install
# 运行项目
npm run dev
如果 npm
太慢的话,可以使用 cnpm
。
如果不知道 cnpm
怎么搞,请自行百度。
<script src="https://unpkg.com/fabric@4.6.0/dist/fabric.min.js"></script>
你可以使用 CDN 的方式引入,因为这样对学习来说是最快捷的。
本文使用该方法!!!
npm i fabric --save
安装完后,package.json
会出现箭头指向的那行代码。
只需 3个操作 就能展示点东西了。
如果是原生项目,使用 <script>
标签引入即可:
<script src="https://unpkg.com/fabric@4.6.0/dist/fabric.min.js"></script>
本文使用了 Vite
构建的项目,所以可以使用 import
引入
import { fabric } from 'fabric'
在 HTML
中创建 <canvas>
,并设置容器的 id
和 宽高,width/height
<canvas width="400" height="400" id="c" style="border: 1px solid #ccc;"></canvas>
这里创建了一个 canvas
容器,**id="c"**。
指定长宽都为 400px
,值得注意的是,这里不需要加 px
这个单位。
style="border: 1px solid #ccc;"
这句其实可以不加,这里只是为了在浏览器看到 canvas
元素到底在哪。
在 JS
中实例化 fabric
,之后就可以使用 fabric
的 api
管理 canvas
了。
<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)。add是“添加”的意思
canvas.add(rect)
}
// 需要在页面容器加载完才能开始初始化(页面加载完才找到 canvas 元素)
// onMounted 是 Vue3 提供的一个页面生命周期函数:实例被挂载后调用。
// onMounted 官方文档说明:https://v3.cn.vuejs.org/guide/composition-api-lifecycle-hooks.html
onMounted(() => {
init() // 执行初始化函数
})
</script>
详情请看代码中每一行注释。
<script setup>
是 Vue 3.2
的一个新语法,普通项目直接使用 <script>
就行了。
就算我不写备注也可以看出 Fabric.js
的代码是极具语义化的,看单词就大概能猜出代码效果。
如果是用原生的 canvas
方法来写,没了解过的同学根本看不懂在写啥。
Fabric.js
的画布操作性是非常强的,这里我列举几个常用例子,其他操作可以查看官方文档。
基础版就是“起步”章节所说的那个例子。
<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' // 引入 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>
<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' // 引入 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) // 将矩形添加到 canvas 画布里
}
onMounted(() => {
init()
})
</script>
创建不可交互的画布,其实只需把 new fabric.Canvas
改成 new fabric.StaticCanvas
即可。
<template>
<canvas id="canvas"></canvas>
</template>
<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric' // 引入 fabric
function init() {
const canvas = new fabric.Canvas('canvas', {
width: 300, // 画布宽度
height: 300, // 画布高度
backgroundColor: '#eee' // 画布背景色
})
// 圆形
const circle = new fabric.Circle({
radius: 30, // 圆的半径
top: 20, // 距离容器顶部 20px
left: 20, // 距离容器左侧 20px
fill: 'pink' // 填充 粉色
})
canvas.add(circle) // 将圆形添加到 canvas 画布里
}
onMounted(() => {
init()
})
</script>
new fabric.Canvas
的第二个参数是用来设置画布基础功能的。更多配置参数可以查看 『官方文档』。
<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(
'https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/27d1b4e5f8824198b6d51a2b1c2d0d75~tplv-k3u1fbpfcp-zoom-crop-mark:400:400:400:400.awebp',
canvas.renderAll.bind(canvas)
)
}
onMounted(() => {
init()
})
</script>
setBackground
Image
这个很好懂,设置背景图片。
需要注意的是,在 Fabric.js
里使用 gif
只会渲染第一帧。
<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(
'https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/27d1b4e5f8824198b6d51a2b1c2d0d75~tplv-k3u1fbpfcp-zoom-crop-mark:400:400:400:400.awebp',
canvas.renderAll.bind(canvas),
{
angle: 15 // 旋转背景图
}
)
}
onMounted(() => {
init()
})
</script>
setBackgroundImage
还有第三个参数,嘿嘿嘿没想到吧
第三个参数除了旋转,还可以设置 scaleX
、scaleY
之类的操作。
更多设置可以查看 『文档』 。
但这个例子存在一个问题,如果图片的尺寸没 canvas
容器大,就填不满,否则就溢出(只显示图片的局部)。
解决方案请看下一个案例。
<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(
'https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/27d1b4e5f8824198b6d51a2b1c2d0d75~tplv-k3u1fbpfcp-zoom-crop-mark:400:400:400:400.awebp',
(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
来加载图片,第一个参数是图片地址,第二个参数是回调函数。
拿到图片的参数和画布的宽高进行计算,从而使图片充满全屏。
<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.setBackgroundColor({
source: 'https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/27d1b4e5f8824198b6d51a2b1c2d0d75~tplv-k3u1fbpfcp-zoom-crop-mark:40:40:40:40.awebp',
repeat: 'repeat'
}, canvas.renderAll.bind(canvas))
}
onMounted(() => {
init()
})
</script>
这个例子使用的图片尺寸是比较小的,所以在 setBackgroundColor
的第3个参数中设置了 repeat: 'repeat'
,表示重复渲染图片。
<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'
import jailCellBars from '@/assets/images/jail_cell_bars.png' // 引入背景图
function init() {
const canvas = new fabric.Canvas('canvas')
canvas.add(
new fabric.Circle({
radius: 30, // 圆形半径
fill: '#f55',
top: 70,
left: 70
})
)
// 设置覆盖图像的画布
canvas.setOverlayImage( // setOverlayImage(image, callback, optionsopt)
jailCellBars, // 图片,script开头import进来的
canvas.renderAll.bind(canvas)
)
}
onMounted(() => {
init()
})
</script>
值得注意的2点:
canvas.setOverlayImage
代替原本的 canvas.setBackgroundImage
。png
,这样就能展示案例所示的效果,背景图叠在图案元素上面。Fabric.js
提供了以下几种基础图形:
<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') // 绑定canvas,传入id
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
创建 矩形。
<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') // 绑定canvas,传入id
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>
画圆角矩形,需要添加 rx
和 ry
,这两个属性的值可以不一样,如果知道 css
圆角的原理,其实对 rx
和 ry
不难理解。
自己修改一下这两个值看看效果理解会更深刻。
<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
设置半径大小。
<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
,但要设置 rx
和 ry
。
rx
> ry
:椭圆是横着的rx
< ry
:椭圆是竖着的rx
= ry
: 看上去就是个圆形<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
创建三角形,三角形是需要给定 “底和高” 的。
<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个参数。
stroke
。
<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
是不会自动把 起始点 和 结束点 自动闭合起来的。
<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
会自动把 起始点 和 结束点 连接起来。
<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 path = new fabric.Path('M 0 0 L 200 100 L 170 200 z')
path.set({
top: 50, // 距离容器顶部距离 50px
left: 50, // 距离容器左侧距离 50px
fill: 'hotpink', // 填充 亮粉色
opacity: 0.5, // 不透明度 50%
stroke: 'black', // 描边颜色 黑色
strokeWidth: 10 // 描边粗细 10px
})
canvas.add(path)
}
onMounted(() => {
init()
})
</script>
使用 new fabric.Path
创建路径。
Fabric.js
有3类跟文本相关的 api
。
<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('雷猴啊')
canvas.add(text)
}
onMounted(() => {
init()
})
</script>
使用 new fabric.Text
创建文本,传入第一个参数就是文本内容。
new fabric.Text
还支持第二个参数,可以设置文本样式,这方面内容将在下一章讲到,往下滑动页面就能见到。
<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('雷猴啊')
canvas.add(itext)
}
onMounted(() => {
init()
})
</script>
使用 new fabric.IText
可以创建可编辑文本,用法和 new fabric.Text
一样。
IText
比 Text
多了个大写 “I” 在首字母上。
<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('Lorum ipsum dolor sit amet', {
width: 250
})
canvas.add(textbox)
}
onMounted(() => {
init()
})
</script>
使用 new fabric.Textbox
可以创建文本框。
new fabric.Textbox
第二个参数是对象,使用 width
可以设定了文本框的宽度,文本内容超过设定的宽度会自动换行。
new fabric.Textbox
的内容同样是可编辑的。
其实样式属性是非常多的,这里只列举常用的属性,其他属性可以自行查阅官方文档。
本例以圆形为例(不要在意配色,我随便输入颜色演示一下)
<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>
上面这个例子的样式分为正常状态和被选中状态,详情请看代码注释。
<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('雷猴', {
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>
除此之外,还可以配置 上划线 、下划线 、删除线 、左对齐 、 右对齐 、 居中对齐 、 行距 等。
<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 = "Lorem ipsum dolor sit amet,\nconsectetur adipisicing elit,\nsed do eiusmod tempor incididunt\nut labo"
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>
上面的上划线、下划线、删除线的配置,可以同时使用。
<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') // 实例化fabric,并绑定到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>
<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') // 实例化fabric,并绑定到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>
r1
、r2
、x1
、y1
、x2
、y2
这几个参数可以自己修改值然后看看效果,自己亲手改一下会理解得更深刻。
<template>
<div>
<canvas width="400" height="400" id="canvas" style="border: 1px solid #ccc;"></canvas>
<img src="@/assets/logo.png" id="logo">
</div>
</template>
<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'
function init() {
const canvas = new fabric.Canvas('canvas')
const imgElement = document.getElementById('logo')
imgElement.onload = function() {
let imgInstance = new fabric.Image(imgElement, {
left: 100,
top: 100,
width: 200,
height: 200,
angle: 50, // 旋转
})
canvas.add(imgInstance)
}
}
onMounted(() => {
init()
})
</script>
<style>
#logo {
display: none;
}
</style>
需要使用 onload
方法监听图片是否加载完成。
只有在图片完全加载后再添加到画布上才能展示出来。
使用该方法,如果不想在画布外展示图片,需要使用 display: none;
把图片隐藏起来。
<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'
import logo from '@/assets/logo.png' // 引入图片
function init() {
const canvas = new fabric.Canvas('canvas')
fabric.Image.fromURL(logo, oImg => {
oImg.scale(0.5) // 缩放
canvas.add(oImg) // 将图片加入到画布
})
}
onMounted(() => {
init()
})
</script>
使用 fabric.Image.fromURL
加载图片。
第一个参数是图片资源,可以放入本地图片,也可以放网络图片;
第二个参数是回调函数,图片加载完就可以对图片对象进行操作。
<template>
<div>
<canvas width="500" height="500" id="canvas" style="border: 1px solid #ccc;"></canvas>
</div>
</template>
<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'
import gwen from '@/assets/images/gwen-spider-verse-ah.jpg'
function init() {
const canvas = new fabric.Canvas('canvas')
fabric.Image.fromURL(gwen, img => {
img.scale(0.5) // 图片缩小50%
canvas.add(img)
})
// 单个滤镜
fabric.Image.fromURL(gwen, img => {
img.scale(0.5) // 图片缩小50%
img.left = 250
// 添加滤镜
img.filters.push(new fabric.Image.filters.Grayscale())
// 图片加载完成之后,应用滤镜效果
img.applyFilters()
canvas.add(img)
})
// 叠加滤镜
// “filters”属性是一个数组,我们可以用数组方法执行任何所需的操作:移除滤镜(pop,splice,shift),添加滤镜(push,unshift,splice),甚至可以组合多个滤镜。当我们调用 applyFilters 时,“filters”数组中存在的任何滤镜将逐个应用,所以让我们尝试创建一个既色偏又明亮(Brightness)的图像。
fabric.Image.fromURL(gwen, img => {
img.scale(0.5) // 图片缩小50%
// 添加滤镜
img.filters.push(
new fabric.Image.filters.Grayscale(),
new fabric.Image.filters.Sepia(), //色偏
new fabric.Image.filters.Brightness({ brightness: 0.2 }) //亮度
)
// 图片加载完成之后,应用滤镜效果
img.applyFilters()
img.set({
left: 250,
top: 250,
})
canvas.add(img)
})
}
onMounted(() => {
init()
})
</script>
给图片添加滤镜,fabric.Image.fromURL
的回调函数里返回一个图片对象,图片对象可以使用 filters
添加滤镜。
fabric 内置滤镜
<template>
<div>
<canvas width="500" height="500" 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')
let triangle = new fabric.Triangle({
top: 100,
left: 100,
width: 80,
height: 100,
fill: 'blue',
angle: 30 // 旋转30度
})
canvas.add(triangle)
}
onMounted(() => {
init()
})
</script>
<template>
<div>
<canvas width="500" height="500" 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')
let triangle = new fabric.Triangle({
top: 100,
left: 100,
width: 80,
height: 100,
fill: 'blue',
scaleX: 2, // x轴方向放大2倍
scaleY: 2 // y轴方向放大2倍
})
canvas.add(triangle)
}
onMounted(() => {
init()
})
</script>
<template>
<div>
<canvas width="500" height="500" 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')
let triangle = new fabric.Triangle({
top: 100,
left: 100,
width: 80,
height: 100,
fill: 'blue',
scaleY: -1 // scale是负数时,图形会反转
})
canvas.add(triangle)
}
onMounted(() => {
init()
})
</script>
可以直接设置元素的 top
和 left
进行平移。
可参照前面的例子。
<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
可以创建一个组(其实有点像 Photoshop 里面的组,把多个图层放在同一个组内,实现同步的操作,比如拖拽、缩放等)。
Fabric.js
的组提供了很多方法,这里列一些常用的:
getObjects()
返回一组中所有对象的数组size()
所有对象的数量contains()
检查特定对象是否在 group
中item()
组中元素forEachObject()
遍历组中对象add()
添加元素对象remove()
删除元素对象fabric.util.object.clone()
克隆我拿其中一个举例:item()
在上一个例子的基础上,把椭圆改成红色,把 “Hello World” 改成 “雷猴,世界”。
<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>
<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>
使用 canvas.getActiveObject()
可以获取画布当前选中的对象,然后再通过 toActiveSelection()
将组打散。
先别管什么 绝对值动画
和 相对值动画
,等学完这节再往下看就知道了。
本节是动画的基础用法。
<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(动画属性, 动画的结束值, [画的详细信息])
第一个参数是要设置动画的属性。
第二个参数是动画的结束值。
第三个参数是一个对象,包括:
{
rom:允许指定可设置动画的属性的起始值(如果我们不希望使用当前值)。
duration:默认为500(ms)。可用于更改动画的持续时间。
onComplete:在动画结束时调用的回调。
easing:缓动功能。
}
<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'
})
// 请注意第二个参数:+=36
rect.animate('angle', '+=360', {
onChange:canvas.renderAll.bind(canvas), // 每次刷新的时候都会执行
duration: 2000, // 执行时间
easing: fabric.util.ease.easeOutBounce, // 缓冲效果
})
canvas.add(rect)
}
onMounted(() => {
init()
})
</script>
这个例子用了 fabric.util.ease.easeOutBounce
缓冲效果。
其实 绝对值动画
和 相对值动画
的用法是差不多的,只是 第二个参数 用法不同。
相对值动画
是把 animate
改成带上运算符的值,这样就会在原基础上做计算。
Fabric.js
提供了一套很方便的事件系统,我们可以用 on
方法可以初始化事件监听器,用 off
方法将其删除。
<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>
Fabric.js
还提供了很多事件,详情可以查看官方案例
<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', {
isDrawingMode: true, // 开启绘图模式
})
// 设置画笔颜色
canvas.freeDrawingBrush.color = '#11999e'
// 设置画笔粗细
canvas.freeDrawingBrush.width = 10
// 画笔投影
canvas.freeDrawingBrush.shadow = new fabric.Shadow({
blur: 10,
offsetX: 10,
offsetY: 10,
affectStroke: true,
color: '#30e3ca',
})
}
onMounted(() => {
init()
})
</script>
在使用 new fabric.Canvas
创建画布时,设置 isDrawingMode: true
就可以开始自由绘画模式。
canvas.freeDrawingBrush
里有一堆属性可以设置画笔样式。
<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>
使用 lockMovementX
禁止对象水平移动。
<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>
使用 lockMovementY
禁止对象垂直移动。
<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: '#3490de'
})
// 禁止旋转
rect.lockRotation = true
canvas.add(rect)
}
onMounted(() => {
init()
})
</script>
使用 lockRotation
禁止对象旋转。
<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: '#ff9a3c'
})
// 禁止水平缩放
rect.lockScalingX = true
canvas.add(rect)
}
onMounted(() => {
init()
})
</script>
使用 lockScalingX
禁止对象水平缩放。
<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: '#f95959'
})
// 禁止垂直缩放
rect.lockScalingY = true
canvas.add(rect)
}
onMounted(() => {
init()
})
</script>
使用 lockScalingY
禁止对象垂直缩放。
要缩放画布,其实是在监听鼠标事件。
这里监听的是鼠标的滚轮事件:mouse:wheel
。
<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>
<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>
本例的需求是,按下
alt键
后才能触发移动画布的功能。
根据这个需求,可以把任务拆解成3步:
鼠标点击 mouse:down
mouse:down
可以监听到。
alt键
。
alt键
,设置一个值记录 开启移动状态
。
x
和 y
轴坐标。
鼠标移动 mouse:move
鼠标x和y轴坐标
。鼠标松开 mouse:up
关闭移动状态
(鼠标点击的第三步)<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>
Fabric.js
创建出来的元素(图形、图片、组等)默认是可以被选中的。
是否可以选中。
选空白位置可以选中吗?
选中后的样式。
<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>
蓝色三角形要鼠标完全放入才能选中
<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 triangle1 = new fabric.Triangle({
top: 100,
left: 50,
width: 80, // 底边宽度
height: 100, // 底边到定点的距离
fill: 'blue',
})
// 选择三角形空白位置的时候无法选中,当perPixelTargetFind设为false后可以选中。默认值是false
triangle1.perPixelTargetFind = true
// 三角形
const triangle2 = new fabric.Triangle({
top: 100,
left: 200,
width: 80, // 底边宽度
height: 100, // 底边到定点的距离
fill: 'green',
})
canvas.add(triangle1, triangle2)
canvas.selectionFullyContained = true // 只选择完全包含在拖动选择矩形中的形状
}
onMounted(() => {
init()
})
</script>
<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>
<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>
<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: 'gray', // 边框颜色
cornerColor: 'black', // 控制角颜色
cornerSize: 12, // 控制角大小
transparentCorners: true // 控制角填充色透明
})
canvas.add(circle)
canvas.setActiveObject(circle) // 选中第一项
}
onMounted(() => {
init()
})
</script>
<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({
selectionBackgroundColor: 'orange' // 选中后,背景色变橙色
})
canvas.add(circle)
canvas.setActiveObject(circle) // 选中第一项
}
onMounted(() => {
init()
})
</script>
<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.hasBorders = false // 取消边框
canvas.add(circle)
canvas.setActiveObject(circle) // 选中第一项
}
onMounted(() => {
init()
})
</script>
没有控制角将意味着无法用鼠标直接操作缩放和旋转,只允许移动操作。
<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>
本例设置了当鼠标在元素上出现 ”等待指针“ 。
<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.hoverCursor = 'wait' // 设置等待指针
canvas.add(circle)
}
onMounted(() => {
init()
})
</script>
<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.hasBorders = circle.hasControls = false
canvas.add(circle)
function animate(e, dir) {
if (e.target) {
fabric.util.animate({
startValue: e.target.get('angle'),
endValue: e.target.get('angle') + (dir ? 10 : -10),
duration: 100
})
fabric.util.animate({
startValue: e.target.get('scaleX'),
endValue: e.target.get('scaleX') + (dir ? 0.2 : -0.2),
duration: 100,
onChange: function(value) {
e.target.scale(value)
canvas.renderAll()
},
onComplete: function() {
e.target.setCoords()
}
})
}
}
canvas.on('mouse:down', function(e) { animate(e, 1) })
canvas.on('mouse:up', function(e) { animate(e, 0) })
}
onMounted(() => {
init()
})
</script>
不允许从画布框选,但允许选中元素。
<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>
<template>
<canvas width="300" height="300" 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')
// 裁剪的图形
// clipPath从对象的中心开始定位,对象originX和originY不起任何作用,而clipPath originX和originY起作用。定位逻辑与fabric.Group相同
const clipPath = new fabric.Circle({
radius: 40,
left: -40,
top: -40
})
// 矩形
const rect = new fabric.Rect({
width: 200,
height: 100,
fill: 'red'
})
// 裁剪矩形
rect.clipPath = clipPath
canvas.add(rect)
}
onMounted(() => {
init()
})
</script>
<template>
<canvas width="300" height="300" 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')
// 裁剪的图形
// clipPath从对象的中心开始定位,对象originX和originY不起任何作用,而clipPath originX和originY起作用。定位逻辑与fabric.Group相同
const clipPath = new fabric.Circle({
radius: 40,
left: -40,
top: -40
})
const group = new fabric.Group([
new fabric.Rect({ width: 100, height: 100, fill: 'red' }),
new fabric.Rect({ width: 100, height: 100, fill: 'yellow', left: 100 }),
new fabric.Rect({ width: 100, height: 100, fill: 'blue', top: 100 }),
new fabric.Rect({
width: 100,
height: 100,
fill: 'green',
left: 100,
top: 100
})
])
// 裁剪一个组
group.clipPath = clipPath
canvas.add(group)
}
onMounted(() => {
init()
})
</script>
<template>
<canvas width="300" height="300" 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 clipPath = new fabric.Group(
[
new fabric.Circle({ radius: 70, top: -70, left: -70 }),
new fabric.Circle({ radius: 40, top: -95, left: -95 }),
new fabric.Circle({ radius: 40, top: 15, left: 15 })
],
{ left: -95, top: -95 }
)
const group = new fabric.Group([
new fabric.Rect({ width: 100, height: 100, fill: 'red' }),
new fabric.Rect({ width: 100, height: 100, fill: 'yellow', left: 100 }),
new fabric.Rect({ width: 100, height: 100, fill: 'blue', top: 100 }),
new fabric.Rect({
width: 100,
height: 100,
fill: 'green',
left: 100,
top: 100
})
])
group.clipPath = clipPath
canvas.add(group)
}
onMounted(() => {
init()
})
</script>
<template>
<canvas width="300" height="300" 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 clipPath = new fabric.Circle({ radius: 70, top: -50, left: -50 })
const innerClipPath = new fabric.Circle({ radius: 70, top: -90, left: -90 })
clipPath.clipPath = innerClipPath
const group = new fabric.Group([
new fabric.Rect({ width: 100, height: 100, fill: 'red' }),
new fabric.Rect({ width: 100, height: 100, fill: 'yellow', left: 100 }),
new fabric.Rect({ width: 100, height: 100, fill: 'blue', top: 100 }),
new fabric.Rect({ width: 100, height: 100, fill: 'green', left: 100, top: 100 }),
])
group.clipPath = clipPath
canvas.add(group)
}
onMounted(() => {
init()
})
</script>
<template>
<canvas width="300" height="300" 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 clipPath = new fabric.Circle({ radius: 100, top: -100, left: -100 })
const small = new fabric.Circle({ radius: 50, top: -50, left: -50 })
const group = new fabric.Group([
new fabric.Rect({ width: 100, height: 100, fill: "red", clipPath: small }),
new fabric.Rect({ width: 100, height: 100, fill: "yellow", left: 100 }),
new fabric.Rect({ width: 100, height: 100, fill: "blue", top: 100 }),
new fabric.Rect({ width: 100, height: 100, fill: "green", left: 100, top: 100 }),
])
group.clipPath = clipPath
canvas.add(group)
}
onMounted(() => {
init()
})
</script>
<template>
<canvas width="300" height="300" 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 clipPath = new fabric.Text(
'Hi I\'m the \nnew ClipPath!\nI hope we\'ll\nbe friends',
{ top: -100, left: -100 }
)
const group = new fabric.Group([
new fabric.Rect({ width: 100, height: 100, fill: "red" }),
new fabric.Rect({ width: 100, height: 100, fill: "yellow", left: 100 }),
new fabric.Rect({ width: 100, height: 100, fill: "blue", top: 100 }),
new fabric.Rect({ width: 100, height: 100, fill: "green", left: 100, top: 100 }),
])
group.clipPath = clipPath
canvas.add(group)
}
onMounted(() => {
init()
})
</script>
<template>
<canvas width="300" height="300" 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', {
backgroundColor: "#ddd"
})
const group = new fabric.Group([
new fabric.Rect({ width: 100, height: 100, fill: "red" }),
new fabric.Rect({ width: 100, height: 100, fill: "yellow", left: 100 }),
new fabric.Rect({ width: 100, height: 100, fill: "blue", top: 100 }),
new fabric.Rect({ width: 100, height: 100, fill: "green", left: 100, top: 100 }),
])
const clipPath = new fabric.Circle({ radius: 100, top: 0, left: 50 })
canvas.clipPath = clipPath
canvas.add(group)
}
onMounted(() => {
init()
})
</script>
<template>
<canvas width="300" height="300" 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', {
backgroundColor: "#ddd"
})
const group = new fabric.Group([
new fabric.Rect({ width: 100, height: 100, fill: "red" }),
new fabric.Rect({ width: 100, height: 100, fill: "yellow", left: 100 }),
new fabric.Rect({ width: 100, height: 100, fill: "blue", top: 100 }),
new fabric.Rect({ width: 100, height: 100, fill: "green", left: 100, top: 100 }),
])
// 裁剪区之外控件可见
canvas.controlsAboveOverlay = true
const clipPath = new fabric.Circle({ radius: 100, top: 0, left: 50 })
canvas.clipPath = clipPath
canvas.add(group)
}
onMounted(() => {
init()
})
</script>
<template>
<canvas width="300" height="300" 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', {
backgroundColor: "#ddd"
})
const clipPath = new fabric.Rect({ width: 100, height: 100, top: 0, left: 0 })
function animateLeft(){
clipPath.animate({
left: 200,
},{
duration: 900,
onChange: canvas.requestRenderAll.bind(canvas),
onComplete: animateRight
})
}
function animateRight(){
clipPath.animate({
left: 0,
},{
duration: 1200,
onChange: canvas.requestRenderAll.bind(canvas),
onComplete: animateLeft
})
}
function animateDown(){
clipPath.animate({
top: 100,
},{
duration: 500,
onChange: canvas.requestRenderAll.bind(canvas),
onComplete: animateUp
})
}
function animateUp(){
clipPath.animate({
top: 0,
},{
duration: 400,
onChange: canvas.requestRenderAll.bind(canvas),
onComplete: animateDown
})
}
const group = new fabric.Group([
new fabric.Rect({ width: 100, height: 100, fill: "red" }),
new fabric.Rect({ width: 100, height: 100, fill: "yellow", left: 100 }),
new fabric.Rect({ width: 100, height: 100, fill: "blue", top: 100 }),
new fabric.Rect({ width: 100, height: 100, fill: "green", left: 100, top: 100 }),
], {
scale: 1.5
})
canvas.controlsAboveOverlay = true
animateLeft()
animateDown()
canvas.clipPath = clipPath
canvas.add(group)
}
onMounted(() => {
init()
})
</script>
<template>
<canvas width="300" height="300" 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 clipPath = new fabric.Rect({ width: 300, height: 300, top: 0, left: 0, absolutePositioned: true })
const clipPath2 = new fabric.Rect({ width: 300, height: 300, top: 0, left: 0, absolutePositioned: true })
fabric.Image.fromURL("http://fabricjs.com/assets/dragon.jpg", function(img){
img.clipPath = clipPath
img.scaleToWidth(300)
canvas.add(img)
})
fabric.Image.fromURL("http://fabricjs.com/assets/dragon2.jpg", function(img){
img.clipPath =clipPath2
img.scaleToWidth(300)
img.top = 150
canvas.add(img)
})
}
onMounted(() => {
init()
})
</script>
<template>
<canvas width="300" height="300" 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 clipPath = new fabric.Circle({ radius: 100, top: -200, left: -220 })
const clipPath2 = new fabric.Circle({ radius: 100, top: 0, left: -20 })
const clipPath3 = new fabric.Circle({ radius: 100, top: 0, left: -220 })
const clipPath4 = new fabric.Circle({ radius: 100, top: -200, left: -20 })
const g = new fabric.Group([ clipPath, clipPath2, clipPath3, clipPath4 ])
g.inverted = true // 颠倒裁剪
fabric.Image.fromURL("http://fabricjs.com/assets/dragon.jpg", function(img) {
img.clipPath = g
img.scaleToWidth(500)
canvas.add(img)
})
}
onMounted(() => {
init()
})
</script>
所谓的序列化其实就是将画布的内容转成 JSON
,方便保存。
但 Fabric.js
除了能将画布转成字符串,还可以输出 base64
和 svg
。
<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>
打开控制台可以看到输出。
本例分别使用了 JSON.stringify()
、canvas.toJSON()
和 canvas.toObject()
进行序列化一个空画布。
Fabric.js
提供了 toJSON
和 toObject
两个方法,把画布及内容转换成 JSON
。
因为本例输出的是一个空画布,所以在输出内容里的 objects
字段是一个空数组。
如果有背景、有图形之类的元素存在,objects
对象里就会出现相应的数据。
详情可查看 🎁 本节案例在线预览 - 序列化
<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>
使用 canvas.toDataURL('png')
可以输出 png
图片。但这个操作可能会打断 canvas
的渲染,所以之后要再执行以下 canvas.requestRenderAll()
。
输出以下内容,可以把这段复制到浏览器地址打开看看
data:image/png;base64,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
<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>
输出 SVG
很简单,直接调用 canvas.toSVG()
即可。
输出:
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="200" height="200" viewBox="0 0 200 200" xml:space="preserve">
<desc>Created with Fabric.js 4.6.0</desc>
<defs>
</defs>
<rect x="0" y="0" width="100%" height="100%" fill="#a5dee5"></rect>
<g transform="matrix(1 0 0 1 60.5 60.5)" >
<rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,128,0); fill-rule: nonzero; opacity: 1;" x="-10" y="-10" rx="0" ry="0" width="20" height="20" />
</g>
<g transform="matrix(1 0 0 1 120.5 120.5)" >
<circle style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,0,0); fill-rule: nonzero; opacity: 1;" cx="0" cy="0" r="40" />
</g>
</svg>
反序列化就是把 JSON
数据渲染到画布上。
通常把从后台请求回来的数据渲染到画布上。
<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 str = '{"version":"4.6.0","objects":[{"type":"rect","version":"4.6.0","originX":"left","originY":"top","left":50,"top":50,"width":20,"height":20,"fill":"green","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},{"type":"circle","version":"4.6.0","originX":"left","originY":"top","left":80,"top":80,"width":80,"height":80,"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,"radius":40,"startAngle":0,"endAngle":6.283185307179586}],"background":"#ddd"}'
// 初始化画布
const canvas = new fabric.Canvas('canvas')
// 反序列化
canvas.loadFromJSON(str)
}
onMounted(() => {
init()
})
</script>
使用 canvas.loadFromJSON()
可以进行反序列化,里面传入一个 JSON格式
的字符串 即可。
本例的 str
保存了一个 Fabric.js
导出的数据。