目录
一、基于canvas画布的实用类Fabric.js的使用Part.1
Fabric.js简介
开始
方法
事件
canvas常用属性
对象属性
图层层级操作
复制和粘贴
二、基于canvas画布的实用类Fabric.js的使用Part.2
锁定
拖拽和缩放画布
分组
动画
图像滤镜
渐变
右键菜单删除
三、基于canvas画布的实用类Fabric.js的使用Part.3
自由绘画
绘制背景图片
绘制文本
绘制线和路径
自由绘制矩形
自由绘制圆形
自由绘制椭圆形
自由绘制三角形
自由绘制多边形
一、锁定
Fabric对象可以添加一些属性进行锁定,例如静止水平移动、静止垂直移动,静止缩放等等
1、静止水平移动(lockMovementX)
let rect = new fabric.Rect({
width: 100,
height: 50,
fill: '#ffde7d',
top: 20,
left: 20
})
rect.lockMovementX = true
canvas.add(rect)
2、静止垂直移动(lockMovementY)
let rect = new fabric.Rect({
width: 100,
height: 50,
fill: '#ffde7d',
top: 20,
left: 20
})
rect.lockMovementY = true
3、静止旋转(lockRotation)
let rect = new fabric.Rect({
width: 100,
height: 50,
fill: '#ff9a3c',
top: 60,
left: 160
})
rect.lockRotation = true
4、静止水平缩放(lockScalingX)
let rect = new fabric.Rect({
width: 100,
height: 50,
fill: '#ffde7d',
top: 20,
left: 20
})
rect.lockScalingX = true
5、静止垂直缩放(lockScalingY)
let rect = new fabric.Rect({
width: 100,
height: 50,
fill: '#f95959',
top: 20,
left: 20
})
rect.lockScalingY = true
6、限制拖动区域

let boundingBox = new fabric.Rect({
top: 100,
left: 100,
width: 600,
height: 400,
fill: '#f95959',
selectable: false
})
let movingBox = new fabric.Rect({
top: 150,
left: 150,
width: 100,
height: 100,
fill: 'yellow',
hasBorders: false,
hasControls: false,
hoverCursor: 'move'
})
this.canvas.add(boundingBox);
this.canvas.add(movingBox);
this.canvas.on("object:moving", (opt) => {
let top = movingBox.top;
let left = movingBox.left;
let topBound = boundingBox.top;
let bottomBound = topBound + boundingBox.height;
let leftBound = boundingBox.left;
let rightBound = leftBound + boundingBox.width;
opt.target.left = Math.min(Math.max(left, leftBound), rightBound - movingBox.width)
opt.target.top = Math.min(Math.max(top, topBound), bottomBound - movingBox.height)
})
二、分组
Groups是Fabric最强大的功能之一,它可以将任意数量的Fabric对象组合在一起,形成一个小组,分组后,所有对象都可以一起移动、修改、缩放、旋转甚至更改其外观等

let group = new fabric.Group([circle, text], {
left: 100,
top: 100,
angle: -10
})
canvas.add(group)
修改分组的某个对象的属性:

group.item(0).set("fill","red");
group.item(1).set({
text:"trololo",
fill:"white"
})
分组时要记住的另一件事是对象的状态。例如,在与图像组成组时,需要确保这些图像已完全加载:

fabric.Image.fromURL(logo, img => {
let img1 = img.scale(0.3).set({left: 0, top: 0})
fabric.Image.fromURL(logo, img => {
let img2 = img.scale(0.3).set({left: 80, top: 0})
fabric.Image.fromURL(logo, img => {
let img3 = img.scale(0.3).set({left: 160, top: 0})
let group = new fabric.Group([img1, img2, img3], {
left: 10,
top: 400
})
canvas.add(group)
})
})
})
三、动画
每个Fabric对象都有一个animate方法,该方法可以动画化该对象,animate(动画属性,动画的结束值,[动画的详细信息])

let rect = new fabric.Rect({
left: 100,
top: 100,
width: 100,
height: 100,
fill: 'red'
})
rect.animate("angle", 45, {
onChange: canvas.renderAll.bind(canvas)
})
canvas.add(rect)
第一个参数是要设置动画的属性。第二个参数是动画的结束值。如果矩形具有-15°的角度,并且我们传递了45,则动画将从-15°变为45°。第三个参数是一个可选对象,用于指定动画的详细信息-持续时间,回调,缓动等
rect.animate("angle", 45, {
from: 0, // 允许指定可设置动画的属性的起始值(如果我们不希望使用当前值)
duration: 1000, // 默认为500(ms),可用于更改动画的持续时间
easing: fabric.util.ease.easeOutBounce, // 缓动功能 easeOutBounce、easeInCubic、easeOutCubic、easeInElastic、easeOutElastic、easeInBounce、easeOutExpo
onChange: canvas.renderAll.bind(canvas), // 在每次刷新时都会执行
onComplete: (e) => { console.log(e) } // 在动画结束时调用的回调
})
animate的一个方便之处在于它还支持相对值

// 向右移动100px
rect.animate('left', '+=100', {
onChange: canvas.renderAll.bind(canvas)
})
// 逆时针旋转5度
rect.animate('angle', '-=5', {
onChange: canvas.renderAll.bind(canvas)
})
四、图像滤镜
fabric.Image的每个实例都具有“ filters”属性,该属性是一个简单的过滤器数组。该阵列中的每个过滤器都是Fabric过滤器之一的实例。或您自己的自定义过滤器的实例。

let url = 'http://localhost:82/public/img/5.png' // 图片url
let base64Url = await this.imgUrlToBase64(url) // 图片base64 url
// 正常照片
fabric.Image.fromURL(url, img => {
img.scale(0.3)
canvas.add(img)
})
// 单个滤镜
fabric.Image.fromURL(base64Url, img => {
img.scale(0.3)
img.left = 300
// 添加滤镜
img.filters.push(new fabric.Image.filters.Grayscale())
// 图片加载完成之后,应用滤镜效果
img.applyFilters()
canvas.add(img)
})
// 叠加滤镜
fabric.Image.fromURL(base64Url, img => {
img.scale(0.3)
img.set({
left: 300,
top: 250,
})
img.filters.push(
new fabric.Image.filters.Grayscale(),
new fabric.Image.filters.Sepia(), //色偏
new fabric.Image.filters.Brightness({ brightness: 0.2 }) //亮度
)
img.applyFilters()
canvas.add(img)
})
(说明:这里图片可能会出错,放本地图片地址会报“Cannot read property 'naturalWidth' of null”的错误,直接放网络图片地址会报“Failed to execute 'texImage2D' on 'WebGLRenderingContext': The image element contains cross-origin data, and may not be loaded.”的错误。解决方法就是将转为Base64格式)
五、渐变
Fabric支持在所有对象上设置填充或描边属性的渐变,首先创建渐变,然后将其分配给填充或描边。
1、线性渐变

// 圆
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: circle1.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)
2、径向渐变

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,
y1: 50,
x2: 50,
y2: 50,
},
colorStops: [
{ offset: 0, color: '#fee140' },
{ offset: 1, color: '#fa709a' }
]
})
circle.set('fill', gradient);
canvas.add(circle)
六、拖拽和缩放画布
1、拖拽画布

<script>
export default {
data() {
return {
lastPosX: 0, // 上次鼠标位置X坐标
lastPosY: 0, // 上次鼠标位置Y坐标
isDragging: false, // 是否可以拖拽画布
}
},
mounted() {
... // 初始化canvas
this.canvas.on('mouse:down', this.onMouseDown)
this.canvas.on('mouse:move', this.onMouseMove)
this.canvas.on('mouse:up', this.onMouseUp)
},
methods: {
// 监听鼠标按下事件
onMouseDown(opt) {
this.lastPosX = opt.e.clientX
this.lastPosY = opt.e.clientY
this.isDragging = true
},
// 监听鼠标移动事件
onMouseMove(opt) {
if (this.isDragging) {
this.canvas.viewportTransform[4] += opt.e.clientX - this.lastPosX
this.canvas.viewportTransform[5] += opt.e.clientY - this.lastPosY
this.canvas.requestRenderAll()
this.lastPosX = opt.e.clientX
this.lastPosY = opt.e.clientY
}
},
// 监听鼠标松开事件
onMouseUp(opt) {
if (this.isDragging) {
this.canvas.setViewportTransform(this.canvas.viewportTransform)
this.isDragging = false
}
}
}
}
</script>
2、以画布中心点为基准手动缩放

<template>
<el-tooltip content="放大" placement="bottom-start">
<span class="iconfont icon-fangda" @click="onManualScale(-100)"></span>
</el-tooltip>
<el-tooltip content="缩小" placement="bottom-start">
<span class="iconfont icon-suoxiao" @click="onManualScale(100)"></span>
</el-tooltip>
</template>
<script>
export default {
// 中心点缩放画布
onManualScale(delta) {
let zoom = canvas.getZoom() // 获取画布当前缩放值
zoom *= 0.999 ** delta
zoom = zoom > 10 ? 10 : (zoom < 0.1 ? 0.1 : zoom) // 最大放大10倍,最小缩小至10%
canvas.zoomToPoint({ // 以画布中心点为基准缩放
x: this.canvasBoxWidth / 2, // canvasBoxWidth 画布宽度
y: this.canvasBoxHeight / 2 // canvasBoxHeight 画布高度
}, zoom)
}
}
</script>
3、以鼠标指针位置为基准缩放

this.canvas.on('mouse:wheel', this.onMouseWheel)
// 监听鼠标放大缩小事件
onMouseWheel(opt) {
let delta = opt.e.deltaY // 滚轮,向上滚一下是 -100,向下滚一下是 100
let zoom = this.canvas.getZoom() // 获取画布当前缩放值
zoom *= 0.999 ** delta
zoom = zoom > 10 ? 10 : (zoom < 0.1 ? 0.1 : zoom) // 最大放大10倍,最小缩小至10%
this.canvas.zoomToPoint({ // 以鼠标指针位置为基准缩放
x: opt.e.offsetX,
y: opt.e.offsetY
}, zoom)
opt.e.preventDefault()
opt.e.stopPropagation()
}
七、右键菜单删除

<template>
<div class="canvas-box" ref="canvasBox">
<canvas id="canvas"></canvas>
<div id="delMenu" ref="delMenu" v-show="isShowDelMenu" :style="delMenuStyle" @contextmenu.prevent="">
<el-button type="iconButton" icon="h-icon-delete" @click="handleDeleteMenu">删除</el-button>
</div>
</div>
</template>
<script>
export default {
name: 'PointerDetail',
data () {
return {
canvas: null,
activeEle: null, // 上次选中元素
isShowDelMenu: false, // 是否显示删除弹窗
delMenuStyle: '' // 删除弹窗定位样式
}
},
mounted() {
this.init()
},
methods: {
// 初始化
init() {
this.canvas = new fabric.Canvas('canvas', {
fireRightClick: true, // 启用右键,button的数字为3
stopContextMenu: true, // 禁止默认右键菜单
})
this.canvas.on('mouse:down', this.onMouseDown)
},
// 监听鼠标按下事件
onMouseDown(opt) {
// 还原上次选中状态
if (this.activeEle) {
this.activeEle.set('fill', 'transparent')
this.canvas.renderAll()
}
this.activeEle = opt.target || null
// 按下鼠标右键
if (opt.button === 3) {
// 点击到非图片控件 显示删除弹窗和填充控件背景色
if (opt.target && opt.target.type !== 'image') {
this.activeEle.set('fill', 'rgba(100, 100, 255, 0.3)')
this.canvas.renderAll()
this.isShowDelMenu = true
this.$nextTick(() => {
this.delMenuStyle = this.getMenuStyle(this.$refs.delMenu, opt)
})
} else {
// 否则隐藏删除弹窗
this.hiddenMenu()
}
// 按下鼠标左键
} else {
this.hiddenMenu()
}
},
// 获取弹窗坐标
getMenuStyle(ele, opt) {
let menuWidth = ele.offsetWidth
let menuHeight = ele.offsetHeight
let pointX = opt.pointer.x + 2
let pointY = opt.pointer.y + 2
if (this.$refs.canvasBox.offsetWidth - pointX <= menuWidth) {
pointX -= menuWidth
}
if (this.$refs.canvasBox.offsetHeight - pointY <= menuHeight) {
pointY -= menuHeight
}
return `left: ${pointX}px; top: ${pointY}px;`
},
// 隐藏菜单
hiddenMenu() {
this.activeEle = null
this.isShowDelMenu = false
},
// 删除选中元素
handleDeleteMenu() {
this.canvas.remove(this.activeEle)
this.canvas.requestRenderAll()
this.hiddenMenu()
}
}
}