fabricjs使用笔记
安装及引用
- 安装:
npm install fabric - 引用:
import { fabric } from 'fabric'
声明画布
<canvas id="canvas" width="600" height="600">
canvas>
// mounted canvasId = new fabric.Canvas('canvas')
绘制图形
Line Rect Circle Triangle Path Text Group Image
- 直线Line
onLine () {
const line = new fabric.Line([10, 10, 100, 100], {
fill: 'green', //填充颜色 stroke: 'green', //笔触颜色 strokeWidth: 2, //笔触宽度 }) canvasId.add(line) }
- 虚线
onLineDash () {
const line = new fabric.Line([10, 10, 100, 100], {
fill: 'green', stroke: 'green', strokeDashArray: [3, 1] // strokeDashArray[a,b] ==> 每隔a个像素空b个像素 }) canvasId.add(line) }
- 方形Rect
onRect () {
const rect = new fabric.Rect({
left: 10, top: 10, fill: 'red', width: 50, height: 50 }) canvasId.add(rect) }
- 圆形Circle
onCircle () {
const circle = new fabric.Circle({
radius: 100, fill: 'yellow', stroke: 'green', strokeWidth: 3, originX: 'center', // 调整中心点的X轴坐标 originY: 'center', // 调整中心点的Y轴坐标 top: 200, left: 200 }) circle.on('selected', function () {
console.log('selected a circle') }) canvasId.add(circle) }
- 三角形
onTriangle () {
const triangle = new fabric.Triangle({
width: 80, height: 100, fill: 'blue', left: 50, top: 50 }) canvasId.add(triangle) }
- 不规则图形Path
onPath () {
// M => 移动命令 L => 线 z => 让图形闭合路径 // 'M 0 0': 把画笔移动到(0, 0)点坐标 // 'L 200 100': 从画笔的坐标画到(200, 100)坐标 const path = new fabric.Path('M 0 0 L 200 100 L 170 200 z') path.set({
left: 120, top: 120, fill: 'red' }) canvasId.add(path) }
文字Text
- 普通文本
-
onText () { const textStr = new fabric.Text('HELLO WORLD!', { fill: 'red', left: 20, top: 20, fontFamily: 'Comic Sans', fontWeight: 'bold', fontSize: 40, fontStyle: 'italic', // 斜体 underline: 'true', shadow: 'rgba(0,0,0,0.2) 5px 5px 5px', stroke: '#5566ff', // 描边 strokeWidth: 1, textAlign: 'right', textBackgroundColor: 'yellowgreen' }) canvasId.add(textStr) }
-
- 可编辑文本
-
fInsertPrice () { const _this = this // const centerObj = this.canvasId.getCenter() const text = new fabric.IText('HELLO WORLD!', { fill: _this.fontColor, fontSize: _this.fontSize, originX: 'center', originY: 'center', left: 100, top: 400 }) this.canvasId.add(text).setActiveObject(text) text.enterEditing() }
-
- 组合Group
onGroup () {
const text = new fabric.Text('HELLO WORLD.', {
fontSize: 30, fill: 'red', originX: 'center', originY: 'center' }) const circle = new fabric.Circle({
radius: 100, fill: 'yellow', stroke: 'green', strokeWidth: 3, originX: 'center', originY: 'center', scaleY: 0.5, }) const group = new fabric.Group([circle, text], {
left: 150, top: 150, angle: 10 }) canvasId.add(group) }
图片Image
本地图片
onImg () {
fabric.Image.fromURL(require('@/assets/logo.jpg'), function (oImg) {
// oImg.scale(0.5)//图片缩小5倍 oImg.scaleToHeight(300, false); //缩放图片的高度到400 oImg.scaleToWidth(300, false); canvasId.add(oImg); oImg.on('selected', function () {
console.log('selected an image') }) }) }
网络图片
onImgNet () {
const image = new Image() image.setAttribute('crossOrigin', 'anonymous') // 解决跨域 image.src = 'https://fileservice.maimiaotech.com/image/_c1e42b37-f2cf-421e-b48a-5934b2b13109-3' const oImg = new fabric.Image(image, {
}) oImg.scaleToHeight(300, false); //缩放图片的高度到400 oImg.scaleToWidth(300, false); canvasId.add(oImg); oImg.on('selected', function () {
console.log('selected an image') }) },
- 上传图片
onImgUpload () { const _this = this var input = document.createElement('input') input.setAttribute('type', 'file') input.setAttribute('accept', 'image/*') input.click() input.onchange = function () { var file = this.files[0] // console.log(file) if (!file.type.match('image.*')) { console.log('只允许图片格式的文件,如 jpg png gif') return false } var reader = new FileReader(); reader.onload = (function () { return function (e) { _this.handleImg(e.target.result) } })(file) reader.readAsDataURL(file); } }, handleImg (img) { fabric.Image.fromURL(img, function (oImg) { // oImg.scale(0.5)//图片缩小5倍 oImg.scaleToHeight(300, false); //缩放图片的高度到400 oImg.scaleToWidth(300, false); canvasId.add(oImg); oImg.on('selected', function () { console.log('selected an image') }) }) }, - 背景图
this.canvasId.setBackgroundImage(oImg, this.canvasId.renderAll.bind(this.canvasId))
事件
画布监听事件
- mouse:down
- mouse:move
- mouse:up
mounted () {
canvasId = new fabric.Canvas('canvas') canvasId.on('mouse:up', function (options) {
console.log(options) }) }
Object监听事件
- after:render:画布重绘后
- object:selected:对象被选中
- object:moving:对象移动
- object:rotating:对象被旋转
- object:added:对象被加入
- object:removed:对象被移除
画布方法
删除
- 按键删除
canvasId.on('mouse:down', options => {
document.onkeydown = e => {
if (e.keyCode === 8) {
canvasId.remove(options.target) this.acTarget = null } } })
- 按钮删除
canvasId.on('mouse:down', options => {
this.acTarget = options.target })
onDel () {
canvasId.remove(this.acTarget) this.acTarget = null }
删除组合
层级
- 上/下/顶/底:
canvasId.sendBackwards(target)
或: target.sendBackwards()
- 下一层: sendBackwards
- 上一层: bringForward
- 底层: sendToBack
- 顶层: bringToFront
// 上移 bringForward onForward () {
const obj = canvasId.getActiveObject() // 获取当前激活的Object canvasId.bringForward(obj) // 或 obj.bringForward() canvasId.renderAll() }
选中的图层是否置顶显示
preserveObjectStacking,参数:
- true: 选中的元素在当前层显示
- false: 置顶显示
new fabric.Canvas('canvasId', {
preserveObjectStacking: true })
置顶被激活元素
onMouseDown () {
const obj = canvasId.getActiveObject() if (obj) {
obj.bringToFront() } }
生成图片
- utils.js
/ * 将base64转换为blob * @param {*} dataurl */ function dataURLtoBlob (dataurl) { var arr = dataurl.split(',') var mime = arr[0].match(/:(.*?);/)[1] var bstr = atob(arr[1]) var n = bstr.length var u8arr = new Uint8Array(n) while (n--) { u8arr[n] = bstr.charCodeAt(n) } return new Blob([u8arr], { type: mime }) } / * 将blob转换为file * @param {*} theBlob * @param {*} fileName */ function blobToFile (theBlob, fileName) { theBlob.lastModifiedDate = new Date() theBlob.name = fileName return theBlob } export { dataURLtoBlob, blobToFile }
生成并下载
onSave () {
const url = canvasId.toDataURL() const blob = this.$Utils.dataURLtoBlob(url) // a标签下载 const elink = document.createElement('a') elink.download = '截图.png' elink.style.display = 'none' elink.href = URL.createObjectURL(blob) document.body.appendChild(elink) elink.click() document.body.removeChild(elink) }
生成并上传
onSave () {
const url = canvasId.toDataURL() const blob = this.$Utils.dataURLtoBlob(url) // const files = this.$Utils.blobToFile(blob, '截图.png') // blob文件 const files = new window.File([blob], 'tmp.png', {
type: blob.type }) // file文件 // 上传 this.imgUpload(files, function (data) {
// ajax const link = IMG_HOST + 'image/' + data.images[0] console.log(link) }) }
缩放
canvasId.setZoom(0.3)
序列化
onStringfy () {
const jsonStr = JSON.stringify(canvasId.toJSON()) this.jsonStr = jsonStr }
- 转化对象时自定义属性丢失处理:
- canvas 的 toDatalessJSON() 、toDatalessObject()、toJSON()、toObject() 都可以有一个参数
- propertiesToIncludeopt:要包含的属性选项,类型为 Array;
- eg: 给对象自定义
myType属性onRect () { const rect = new fabric.Rect({ left: 10, top: 10, fill: 'red', width: 50, height: 50, myPrice: true }) canvasId.add(rect) } onStringfy () { const jsonStr = JSON.stringify(canvasId.toJSON(['myPrice'])) this.jsonStr = jsonStr }
- canvas 的 toDatalessJSON() 、toDatalessObject()、toJSON()、toObject() 都可以有一个参数
反序列化
onParse () {
canvasId.loadFromJSON('序列化字符串') }
若紧跟着需要设置元素,需在回调函数中操作
fLoadJson (jsonStr) {
this.fClearCanvas() this.canvasId.loadFromJSON(jsonStr, () => {
this.canvasId.forEachObject(item => {
if (item.myMainImg) item.set('selectable', false) }) }) }
其他
- 获取所有元素:
canvasId.getObjects() - 遍历
canvasId.forEachObject(item => {})-
fUpdatePrice (price) { this.canvasId.forEachObject(item => { if (item.myPrice) { // 自定义的myPrice属性 this.canvasId.setActiveObject(item) item.set('text', price + '') item.set('dirty', true) this.canvasId.renderAll() } }) }
-
- 清空画布:
this.canvasId.clear()
元素方法
设置选取框样式
- borderColor: 边框颜色
- editingBorderColor: 编辑框颜色
- borderDashArray: 选取框线性 ==> 虚线
- padding: 内边距
- cornerSize: 控点的大小
- cornerColor: 选取框 ==> 控点背景色 需结合 transparentCorners: false 才能生效
fInsertText (pos) {
console.log(129) const _this = this // const centerObj = this.canvasId.getCenter() const text = new fabric.IText('', {
borderColor: '#000000', editingBorderColor: '#000000', transparentCorners: false, cornerColor: '#ffffff', borderDashArray: [3, 3], cornerSize: 5, padding: 8, fill: _this.fontColor, fontSize: _this.fontSize, originX: 'center', originY: 'center', left: pos.x, top: pos.y }) this.canvasId.add(text).setActiveObject(text) text.enterEditing() }
更改元素内容
obj.set({})
/ * 设置字体颜色 * @param {String} color 字体颜色 */ fFontColor (color) {
const obj = this.canvasId.getActiveObject() if (!obj) return if (obj.type === 'text' || obj.type === 'i-text') {
obj.set({
fill: color, dirty: true // }) this.canvasId.renderAll() } }
判断元素类型
acObj.isType(type)
- eg: 选取多个object时,删除元素需要遍历
fDelTarget: function () {
const acObj = this.canvasId.getActiveObject() if (acObj.isType('activeSelection')) {
// 多个object acObj.forEachObject(item => {
this.canvasId.remove(item) }) } else {
// 单个object this.canvasId.remove(acObj) } }
快速设置元素坐标
变换originX/Y
/ * 处理快捷方位的坐标 * @param {Number} index 九宫格方位 1-9 */ fPosition (index) {
const centerObj = this.canvasId.getCenter() const acObj = this.canvasId.getActiveObject() if (!acObj) return const xObj = {
1: 'left', 2: 'center', 3: 'right', 4: 'left', 5: 'center', 6: 'right', 7: 'left', 8: 'center', 9: 'right' } const yObj = {
1: 'top', 2: 'top', 3: 'top', 4: 'center', 5: 'center', 6: 'center', 7: 'bottom', 8: 'bottom', 9: 'bottom' } const posObj = {
1: {
y: 0, x: 0 }, 2: {
y: 0, x: centerObj.left }, 3: {
y: 0, x: centerObj.left * 2 }, 4: {
y: centerObj.top, x: 0 }, 5: {
y: centerObj.top, x: centerObj.left }, 6: {
y: centerObj.top, x: centerObj.left * 2 }, 7: {
y: centerObj.top * 2, x: 0 }, 8: {
y: centerObj.top * 2, x: centerObj.left }, 9: {
y: centerObj.top * 2, x: centerObj.left * 2 } } acObj.set({
originX: xObj[index], originY: yObj[index], top: posObj[index].y, left: posObj[index].x, dirty: true }) acObj.setCoords() // 防止 编程式定位后无法在新位置被选中 this.canvasId.renderAll() }
固定originX/Y
方便后端绘图,所以要求固定
originX = 'left'originY = 'top'
/ * 处理快捷方位的坐标 * @param {Number} index 九宫格方位 1-9 */ fPosition (index) {
const acObj = this.canvasId.getActiveObject() if (!acObj) return const centerObj = this.canvasId.getCenter() // this.GCanvasSize.scaleL是画布缩放比例,若没有缩放就不需要相乘 // const centerObjL = centerObj.left * this.GCanvasSize.scaleL // const centerObjT = centerObj.top * this.GCanvasSize.scaleL const centerObjL = centerObj.left const centerObjT = centerObj.top const acObjW = acObj.width const acObjH = acObj.height let left = 0; let top = 0 switch (index) {
case 1: case 4: case 7: left = 0; break case 2: case 5: case 8: left = centerObjL - acObjW * 0.5; break case 3: case 6: case 9: left = centerObjL * 2 - acObjW; break default: left = 0 } switch (index) {
case 1: case 2: case 3: top = 0; break case 4: case 5: case 6: top = centerObjT - acObjH * 0.5; break case 7: case 8: case 9: top = centerObjT * 2 - acObjH; break default: top = 0 } acObj.set({
top, left, dirty: true }) acObj.setCoords() // 防止 编程式定位后无法在新位置被选中 this.canvasId.renderAll() },
编程式定位后无法在新位置被选中
acObj.setCoords()
const acObj = FEB.canvasId.getActiveObject() if (!acObj) return acObj.set({
top: 100, left: 100, dirty: true }) acObj.setCoords() // 要紧!要紧! this.canvasId.renderAll()
待探究
- 撤销上一步操作功能
- 动画
- 滤镜
- 高级画板
参考
- 使用笔记
- 官方demo
- 官方文档
其他
gitee地址
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/214989.html原文链接:https://javaforall.net
