目录
一、Fabric.js使用说明Part 1
Fabric.js简介
开始
方法
事件
canvas常用属性
对象属性
图层层级操作
复制和粘贴
二、Fabric.js使用说明Part 2
锁定
拖拽和缩放画布
分组
动画
图像滤镜
渐变
右键菜单删除
三、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、easeOutExpoonChange: 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 radialgradientUnits: '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() {... // 初始化canvasthis.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.clientXthis.lastPosY = opt.e.clientYthis.isDragging = true},// 监听鼠标移动事件onMouseMove(opt) {if (this.isDragging) {this.canvas.viewportTransform[4] += opt.e.clientX - this.lastPosXthis.canvas.viewportTransform[5] += opt.e.clientY - this.lastPosYthis.canvas.requestRenderAll()this.lastPosX = opt.e.clientXthis.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 ** deltazoom = 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,向下滚一下是 100let zoom = this.canvas.getZoom() // 获取画布当前缩放值zoom *= 0.999 ** deltazoom = 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的数字为3stopContextMenu: 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 = truethis.$nextTick(() => {this.delMenuStyle = this.getMenuStyle(this.$refs.delMenu, opt)})} else {// 否则隐藏删除弹窗this.hiddenMenu()}// 按下鼠标左键} else {this.hiddenMenu()}},// 获取弹窗坐标getMenuStyle(ele, opt) {let menuWidth = ele.offsetWidthlet menuHeight = ele.offsetHeightlet pointX = opt.pointer.x + 2let pointY = opt.pointer.y + 2if (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 = nullthis.isShowDelMenu = false},// 删除选中元素handleDeleteMenu() {this.canvas.remove(this.activeEle)this.canvas.requestRenderAll()this.hiddenMenu()}}
}