写点什么

Fabric.js 从入门到 ________

用户头像
学习委员
关注
发布于: 5 小时前
Fabric.js 从入门到________

简介

首先要说的是:本文篇幅很长,建议点赞收藏(点赞就等于学会了)

Fabric.js 简介

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 npm地址』


『Fabric.js github地址』


🎁本文案例在线预览


🎁本文所有案例仓库地址 【欢迎Star,不定期更新!!!】




开发环境搭建

环境和版本说明

  • 本文使用 Fabric.js 的版本是 4.6

  • 本文的开发环境是使用 Vite 构建的 Vue 3.2 项目。


没有 ViteVue3.2 基础的同学也不用怕,因为 Vite 真的足够简单。


本文的目的是讲解 Fabric.js ,所以用到 Vue 3.2 的地方其实很少,用到时我也会有详细说明。


如果你不打算使用 ViteVue 3.2 也没关系,用你喜欢的方式去搭建项目即可


现在只需跟着以下步骤搭建项目即可。

搭建环境(Vite + Vue3)

Vite 官网


Vue3 官网


如果你不想使用 Vite + Vue3 的话,可以跳过本节。


我也建议你直接使用原生 (HTML+CSS+JS) 的方式直接学习 Fabric.js,因为这样上手速度最快。

1. 搭建 Vite 项目

npm init @vitejs/app
复制代码


2. 给项目起个名,并选择 Vue


之后会让你选 vue 或者 vue + ts,我选择了 vue ,你随意。


为什么不选 ts ?因为一人开发的练手项目使用 ts 有点得不偿失。

3. 初始化项目

其实做完上一步就会给出提示(3 条命令),跟着敲完就能运行项目了


# 进入项目目录cd fabric-demo
# 初始化项目npm install
# 运行项目npm run dev
复制代码


如果 npm 太慢的话,可以使用 cnpm


如果不知道 cnpm 怎么搞,请自行百度。

安装 Fabric.js

方式 1:CDN

<script src="https://unpkg.com/fabric@4.6.0/dist/fabric.min.js"></script>
复制代码


你可以使用 CDN 的方式引入,因为这样对学习来说是最快捷的。

方式 2:npm

本文使用该方法!!!


npm i fabric --save
复制代码



安装完后,package.json 会出现箭头指向的那行代码。




起步

只需 3 个操作 就能展示点东西了。

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

如果是原生项目,使用 <script> 标签引入即可:


<script src="https://unpkg.com/fabric@4.6.0/dist/fabric.min.js"></script>
复制代码


本文使用了 Vite 构建的项目,所以可以使用 import 引入


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


2. 创建 canvas 容器

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 元素到底在哪。


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

JS 中实例化 fabric ,之后就可以使用 fabricapi 管理 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.htmlonMounted(() => { init() // 执行初始化函数})</script>
复制代码


详情请看代码中每一行注释。


<script setup>Vue 3.2 的一个新语法,普通项目直接使用 <script> 就行了。



就算我不写备注也可以看出 Fabric.js 的代码是极具语义化的,看单词就大概能猜出代码效果。


如果是用原生的 canvas 方法来写,没了解过的同学根本看不懂在写啥。




画布

Fabric.js 的画布操作性是非常强的,这里我列举几个常用例子,其他操作可以查看官方文档。


『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 即可。


在 js 设定画布参数


<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>
复制代码


setBackgroundImage 这个很好懂,设置背景图片。


需要注意的是,在 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 还有第三个参数,嘿嘿嘿没想到吧



第三个参数除了旋转,还可以设置 scaleXscaleY 之类的操作。


更多设置可以查看 『文档』


但这个例子存在一个问题,如果图片的尺寸没 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 点:


  1. 使用 canvas.setOverlayImage 代替原本的 canvas.setBackgroundImage

  2. 所使用的图片最好是带透明层的 png ,这样就能展示案例所示的效果,背景图叠在图案元素上面。



🎁 本例所使用的图片地址




基础图形

Fabric.js 提供了以下几种基础图形:



<br>


🎁 本节案例在线预览 - 基础图形


🎁 本节代码仓库

矩形


<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>
复制代码


画圆角矩形,需要添加 rxry,这两个属性的值可以不一样,如果知道 css 圆角的原理,其实对 rxry 不难理解。


自己修改一下这两个值看看效果理解会更深刻。


圆形


<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 ,但要设置 rxry


  • 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 个参数。


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

  • 第二个参数是 线段的样式,要设置线段的颜色,需要使用 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 会自动把 起始点结束点 连接起来。




绘制路径

『Fabric.js 路径Path - 文档』


🎁 本节案例在线预览 - 绘制路径


🎁 本节代码仓库



<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 创建路径。


  • M:可以理解为新的起始点 x,y 坐标

  • L:每个折点的 x,y 坐标

  • z:自动闭合(自动把结束点和起始点连接起来)




文本

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


  • 普通文本

  • 可编辑文本

  • 文本框

普通文本 Text

『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 text = new fabric.Text('雷猴啊') canvas.add(text)}
onMounted(() => { init()})</script>
复制代码


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


new fabric.Text 还支持第二个参数,可以设置文本样式,这方面内容将在下一章讲到,往下滑动页面就能见到。

可编辑文本 IText

『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 itext = new fabric.IText('雷猴啊') canvas.add(itext)}
onMounted(() => { init()})</script>
复制代码


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


ITextText 多了个大写 “I” 在首字母上。

文本框 Textbox

『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 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>
复制代码


上面的上划线、下划线、删除线的配置,可以同时使用。




渐变

『Fabric.js 渐变 - 文档』


🎁 本节案例在线预览 - 渐变


🎁 本节代码仓库

线性渐变


<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>
复制代码


r1r2x1y1x2y2 这几个参数可以自己修改值然后看看效果,自己亲手改一下会理解得更深刻。




使用图片

『Fabric.js 图片 - 文档』


🎁 本节案例在线预览 - 使用图片


🎁 本节代码仓库

方法 1:使用 HTML 的图片


<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; 把图片隐藏起来。

方法 2:使用 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'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 加载图片。


第一个参数是图片资源,可以放入本地图片,也可以放网络图片;


第二个参数是回调函数,图片加载完就可以对图片对象进行操作。




图片滤镜

『Fabric.js 图片路径 - 文档』


🎁 本节案例在线预览 - 图片滤镜


🎁 本节代码仓库



<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 内置滤镜


  • BaseFilter 基本过滤器

  • Blur 模糊

  • Brightness 亮度

  • ColorMatrix 颜色矩阵

  • Contrast 对比

  • Convolute 卷积

  • Gamma 伽玛

  • Grayscale 灰度

  • HueRotation 色调旋转

  • Invert 倒置

  • Noise 噪音

  • Pixelate 像素化

  • RemoveColor 移除颜色

  • Resize 调整大小

  • Saturation 饱和

  • Sepia 色偏




转换

旋转角度 angle


<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>
复制代码


缩放 scaleX 和 scaleY


<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>
复制代码


反转 scaleX 和 scaleY


<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

可以直接设置元素的 topleft 进行平移。


可参照前面的例子。




分组

『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')
// 椭圆 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() 将组打散。




动画

『Fabric.js animate - 文档』


🎁 本节案例在线预览 - 动画


🎁 本节代码仓库

绝对值动画

先别管什么 绝对值动画相对值动画 ,等学完这节再往下看就知道了。


本节是动画的基础用法。



<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' })
// 请注意第二个参数:+=360 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 还提供了很多事件,详情可以查看官方案例





自由绘画

『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


  1. 该步骤使用 mouse:down 可以监听到。

  2. 在回调函数里监听是否按下 alt键

  3. 如果按下 alt键 ,设置一个值记录 开启移动状态

  4. 记录鼠标当前所在的 xy 轴坐标。


鼠标移动 mouse:move


  1. 判断是否需要移动(鼠标点击的第三步)。

  2. 如需移动,立刻转换画布视图模式

  3. 将画布移动到 鼠标x和y轴坐标


鼠标松开 mouse:up


  1. 把画布定格在鼠标松开的坐标。

  2. 关闭移动状态(鼠标点击的第三步)



<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>
复制代码




裁剪

『Fabric.js 裁剪原文 1』


『Fabric.js 裁剪原文 2』


🎁 本节案例在线预览 - 裁剪


🎁 本节代码仓库

裁剪单一图形


<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>
复制代码


颠倒的 clipPaths


<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 除了能将画布转成字符串,还可以输出 base64svg

输出 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 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 提供了 toJSONtoObject 两个方法,把画布及内容转换成 JSON


因为本例输出的是一个空画布,所以在输出内容里的 objects 字段是一个空数组。


如果有背景、有图形之类的元素存在,objects 对象里就会出现相应的数据。


详情可查看 🎁 本节案例在线预览 - 序列化


输出 png(base64 版)


<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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
复制代码


输出 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', { 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 导出的数据。




收藏 = 懂了


点赞 = 熟练掌握

发布于: 5 小时前阅读数: 3
用户头像

学习委员

关注

反派 2019.03.19 加入

用键盘绣花

评论

发布
暂无评论
Fabric.js 从入门到________