# fabricjs **Repository Path**: denghuoan/fabricjs ## Basic Information - **Project Name**: fabricjs - **Description**: fabricjs项目 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 5 - **Forks**: 1 - **Created**: 2021-03-12 - **Last Updated**: 2025-07-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # fabricjs使用笔记 ## 安装及引用 - 安装: `npm install fabric` - 引用: `import { fabric } from 'fabric'` ## 声明画布 ```html ``` ```js // mounted canvasId = new fabric.Canvas('canvas') ``` ## 绘制图形 > Line Rect Circle Triangle Path Text Group Image - 直线Line ```js onLine () { const line = new fabric.Line([10, 10, 100, 100], { fill: 'green', //填充颜色 stroke: 'green', //笔触颜色 strokeWidth: 2, //笔触宽度 }) canvasId.add(line) } ``` - 虚线 ```js 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 ```js onRect () { const rect = new fabric.Rect({ left: 10, top: 10, fill: 'red', width: 50, height: 50 }) canvasId.add(rect) } ``` - 圆形Circle ```js 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) } ``` - 三角形 ```js onTriangle () { const triangle = new fabric.Triangle({ width: 80, height: 100, fill: 'blue', left: 50, top: 50 }) canvasId.add(triangle) } ``` - 不规则图形Path ```js 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 - 普通文本 - ```js 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) } ``` - 可编辑文本 - ```js 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 ```js 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 #### 本地图片 ```js 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') }) }) } ``` #### 网络图片 ```js onImgNet () { const image = new Image() image.setAttribute('crossOrigin', 'anonymous') // 解决跨域 image.src = 'https://fileservice.maimiaotech.com/image/20210114_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') }) }, ``` - 上传图片 ```js 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') }) }) }, ``` - 背景图 ```js this.canvasId.setBackgroundImage(oImg, this.canvasId.renderAll.bind(this.canvasId)) ``` ## 事件 ### 画布监听事件 - mouse:down - mouse:move - mouse:up ```js 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:对象被移除 ## 画布方法 ### 删除 - 按键删除 ```js canvasId.on('mouse:down', options => { document.onkeydown = e => { if (e.keyCode === 8) { canvasId.remove(options.target) this.acTarget = null } } }) ``` - 按钮删除 ```js canvasId.on('mouse:down', options => { this.acTarget = options.target }) ``` ```js onDel () { canvasId.remove(this.acTarget) this.acTarget = null } ``` #### 删除组合 ### 层级 - 上/下/顶/底: > canvasId.sendBackwards(target) > > 或: target.sendBackwards() - 下一层: sendBackwards - 上一层: bringForward - 底层: sendToBack - 顶层: bringToFront ```js // 上移 bringForward onForward () { const obj = canvasId.getActiveObject() // 获取当前激活的Object canvasId.bringForward(obj) // 或 obj.bringForward() canvasId.renderAll() } ``` #### 选中的图层是否置顶显示 preserveObjectStacking,参数: - true: 选中的元素在当前层显示 - false: 置顶显示 ```js new fabric.Canvas('canvasId', { preserveObjectStacking: true }) ``` #### 置顶被激活元素 ```js onMouseDown () { const obj = canvasId.getActiveObject() if (obj) { obj.bringToFront() } } ``` ### 生成图片 - utils.js ```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 } ``` #### 生成并下载 ```js 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) } ``` #### 生成并上传 ```js 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) }) } ``` ### 缩放 ```js canvasId.setZoom(0.3) ``` ### 序列化 ```js onStringfy () { const jsonStr = JSON.stringify(canvasId.toJSON()) this.jsonStr = jsonStr } ``` - 转化对象时自定义属性丢失处理: - canvas 的 toDatalessJSON() 、toDatalessObject()、toJSON()、toObject() 都可以有一个参数 - propertiesToIncludeopt:要包含的属性选项,类型为 Array; - eg: 给对象自定义`myType属性` ```js 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 } ``` ### 反序列化 ```js onParse () { canvasId.loadFromJSON('序列化字符串') } ``` 若紧跟着需要设置元素,需在回调函数中操作 ```js fLoadJson (jsonStr) { this.fClearCanvas() this.canvasId.loadFromJSON(jsonStr, () => { this.canvasId.forEachObject(item => { if (item.myMainImg) item.set('selectable', false) }) }) } ``` ### 其他 - 获取所有元素: `canvasId.getObjects()` - 遍历` canvasId.forEachObject(item => {})` - ```js 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 才能生效 ```js 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({}) ```js /** * 设置字体颜色 * @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时,删除元素需要遍历 ```js 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 ```js /** * 处理快捷方位的坐标 * @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' ` ```js /** * 处理快捷方位的坐标 * @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() ```js const acObj = FEB.canvasId.getActiveObject() if (!acObj) return acObj.set({ top: 100, left: 100, dirty: true }) acObj.setCoords() // 要紧!要紧! this.canvasId.renderAll() ``` ## 参考 - [使用笔记](https://github.com/vipstone/drawingboard/blob/master/fabricjs%E4%BD%BF%E7%94%A8%E7%AC%94%E8%AE%B0.md) - [官方demo](http://fabricjs.com/) - [官方文档](http://fabricjs.com/docs/) ## 其他 [最新笔记](https://blog.csdn.net/denghuocc/article/details/114702525)