fabricjs使用笔记

fabricjs使用笔记fabricjs 使用笔记安装及引用安装 npminstallfa 引用 import fabric from fabric 声明画布 canvasid canvas width 600 height 600 mountedcanva newfabric Canvas canvas 绘制图形 LineRectCirc canvasid canvas width 600 height 600

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 } 

反序列化

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

(0)
上一篇 2026年3月18日 下午2:57
下一篇 2026年3月18日 下午2:58


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号