vue2+Fabric.js库的使用(6)--Fabric的画板项目搭建2


前言

上文进行了基础图形的自由绘制,本文会基于文本和折线进行自由的绘制,本文是基于上文进行的,相当于补充。


提示:以下是本篇文章正文内容,下面案例可供参考

一、文本的绘制

文本的绘制相对比较简单,只需要监测鼠标按下即可,即鼠标按下就可以进行文本输出了,再把输入的文本存入栈中就可以了

        //鼠标按下
        'mouse:down': opt => {

          this.downPoint = opt.absolutePointer //记录下鼠标按下时的坐标

          //对文字对象的判断 判断是编辑还是新增
          this.isTextObj(opt)

          this.drawing()  //绘制图形
        },

需要定义输入文本的方法 isTextObj(),如下:

      //判断上次的文字对象内容和编辑状态
      isTextObj(opt) {
        
        if (this.textObj !== '') {
          if (this.textObj.isEditing === true) {
            this.textObj.exitEditing()
            //将文字对象存在栈中
            this.backStack.push(this.textObj)
          }
          if (this.textObj.text === '') {
            this.canvas.remove(this.textObj) //空文本删除
            this.textObj = ''
          }
        }
        //判断当前点击的对象是不是文字
        if (opt.target) {
          this.textObj = opt.target
        }
      },

这样处理之后还有个问题,就是会出现不灵敏,还需要在鼠标抬起的时候进行过滤判断:

          if (!this.patternClass || this.clickMenubarName == 'text') {
            //将自由绘画的对象存在栈中
            if (!this.patternClass && this.canvas.selection === false) {
              //比较对象长度是否发生变化,是否有新增
              let getObj = this.canvas.getObjects()
              if (this.canvasObjLength !== getObj.length) {
                this.canvasObjLength = getObj.length
                //将最新的自由绘画对象存储在back栈中
                this.backStack.push(getObj[getObj.length - 1])
              }
            }
            this.patternClass = null
            return
          }

效果就比较好了:
在这里插入图片描述

二、折线的绘制

折线的绘制相对是复杂一点的:

首先是鼠标按下,鼠标按下需要判断是否是第一次按下,如果是第一次按下,则相当于是重新开始绘制,需要把之前绘制的点位清零,如果不是第一次绘制,则是把当前按下的点位存储并进行渲染

然后是鼠标移动,鼠标移动的时候,需要不断的根据鼠标的位置重绘图形

然后是双击,鼠标双击的时候,需要停止绘制,表示这次的绘制完成

然后鼠标抬起这个是不需要做任何事情的

下面分别编写代码:

1、鼠标按下的时候
鼠标按下,需要判断是不是第一次绘制,然后做出不同的反应

          if(this.clickMenubarName == 'broken'){
            if(this.patternClass === null){
            this.drawing()
            }else{
            this.patternFun(opt)
            }
          }

根据patternClass 是否为空判断,如果是第一次绘制,则开始绘制;如果不是第一次绘制,则需要加载绘制【这个方法和其它图形的重新渲染的放在一起的】

开始绘制的代码如下:

          case 'broken':      //绘制折线
            const currentPoint = this.downPoint
            this.patternClass = new fabric.Polyline(
              [
                { x: currentPoint.x, y: currentPoint.y },
                { x: currentPoint.x, y: currentPoint.y }
              ],
              {
                fill: 'transparent',
                stroke: this.borderColor,
                strokeWidth:this.borderWidth,
                objectCaching: false
              }
            )
            this.canvas.add(this.patternClass)
            break;

加载绘制的代码如下:

          case 'broken':
            //按下的时候只是修改最后一个节点的值
            const currentPoint = e.absolutePointer
            let points = this.patternClass.points
            points.push({
              x: currentPoint.x,
              y: currentPoint.y
            })
            this.canvas.requestRenderAll()
            break

2、鼠标移动的时候
移动的时候,需要重新渲染图形,根据鼠标的位置

        //鼠标移动
        'mouse:move': opt => {
          //图案绘制
          if(this.clickMenubarName != 'broken'){
            this.patternFun(opt)
          }
          if(this.clickMenubarName == 'broken'){
            this.changePolylineBelt(opt)
          }
        },

移动执行的方法changePolylineBelt()如下:

      //折线移动
      changePolylineBelt(e){
        const currentPoint = e.absolutePointer
        let points = this.patternClass.points

        let newX = currentPoint.x
        let newY = currentPoint.y  

        points[points.length - 1].x = newX
        points[points.length - 1].y = newY

        this.canvas.requestRenderAll()
      },

3、鼠标双击的时候
鼠标双击的时候,执行额按成绘制的方法

        //鼠标双击
        'mouse:dblclick': opt => {
          this.finishPolyline(opt)
        },

完成绘制的方法finishPolyline()如下:

      // 完成折线绘制
      finishPolyline(e){
        const currentPoint = e.absolutePointer
        let points = this.patternClass.points
        points[points.length - 1].x = currentPoint.x
        points[points.length - 1].y = currentPoint.y
        points.pop()
        points.pop()
        // 按需添加自闭合代码
        // if (points[0].x != points[points.length - 1].x && points[0].y != points[points.length - 1].y) {
            // changepatternClass({ absolutePointer: { x: points[0].x, y: points[0].y } })
        // }
        this.canvas.remove(this.patternClass)
        if (points.length > 1) {
          this.patternClass = null
          this.patternClass = new fabric.Polyline(points, {
              fill: 'transparent',
              stroke: this.borderColor,
              strokeWidth:this.borderWidth,
              objectCaching: false
          })
          this.canvas.add(this.patternClass)
        }
        this.patternClass.style=this.clickMenubarName
        //将当前对象压入栈中
        this.backStack.push(this.patternClass)
        this.patternClass = null
        this.canvas.requestRenderAll()
      },

这样,折线的绘制就完成了,效果如下:
在这里插入图片描述

完整代码

图形绘制到这里就结束了,完整的代码如下:

  methods:{
    init(){
      this.canvas = new fabric.Canvas('canvas-box',{
        targetFindTolerance: 10, //当元素以实际内容选择时,这个值越大越表面容易被选择到
        perPixelTargetFind: true // 选择绘画对象时,以对象实际内容选择,而不是所处边界
      }) // 这里传入的是canvas的id

      this.canvas.on({
        //鼠标按下
        'mouse:down': opt => {

          this.downPoint = opt.absolutePointer //记录下鼠标按下时的坐标

          //对文字对象的判断 判断是编辑还是新增
          this.isTextObj(opt)

          this.drawing()  //绘制图形

          if(this.clickMenubarName == 'broken'){
            if(this.patternClass === null){
            this.drawing()
            }else{
            this.patternFun(opt)
            }
          }
        },

        //鼠标移动
        'mouse:move': opt => {
          //图案绘制
          if(this.clickMenubarName != 'broken'){
            this.patternFun(opt)
          }
          if(this.clickMenubarName == 'broken'){
            this.changePolylineBelt(opt)
          }
        },

        //鼠标抬起
        'mouse:up': opt => {

          if (!this.patternClass || this.clickMenubarName == 'text') {
            //将自由绘画的对象存在栈中
            if (!this.patternClass && this.canvas.selection === false) {
              //比较对象长度是否发生变化,是否有新增
              let getObj = this.canvas.getObjects()
              if (this.canvasObjLength !== getObj.length) {
                this.canvasObjLength = getObj.length
                //将最新的自由绘画对象存储在back栈中
                this.backStack.push(getObj[getObj.length - 1])
              }
            }
            this.patternClass = null
            return
          }

          if(this.clickMenubarName != 'broken'){
            if (JSON.stringify(this.downPoint) === JSON.stringify(opt.absolutePointer)) {
            this.canvas.remove(this.patternClass)
          } else {
            //将当前对象压入栈中
            this.backStack.push(this.patternClass)
          }
          //将创建的对象置空
          this.patternClass = null
          }
        },

        //鼠标双击
        'mouse:dblclick': opt => {
          this.finishPolyline(opt)
        },

      })
    },

      //创建图形
      drawing() {
        switch (this.clickMenubarName) {
          case 'rect':  //绘制矩形
            this.patternClass = new fabric.Rect({
              top: this.downPoint.y, //创建对象的坐标
              left: this.downPoint.x,
              width: 0, //宽和高
              height: 0,
              fill: 'transparent', //填充颜色
              stroke: this.borderColor, //线条颜色
              strokeWidth: this.borderWidth //线条宽度
            })
            this.canvas.add(this.patternClass)
            break
          case 'ellipse':   //绘制椭圆形
            this.patternClass = new fabric.Ellipse({
              top: this.downPoint.y,
              left: this.downPoint.x,
              rx: 0, //椭圆的两个中心点
              ry: 0,
              fill: 'transparent',
              stroke: this.borderColor,
              strokeWidth: this.borderWidth
            })
            this.canvas.add(this.patternClass)
            break
          case 'line':    //绘制直线
            this.patternClass = new fabric.Line(
              [
                this.downPoint.x, //直线的开始坐标
                this.downPoint.y,
                this.downPoint.x, //直线的结束坐标
                this.downPoint.y
              ],
              {
                fill: 'transparent',
                stroke: this.borderColor,
                strokeWidth: this.borderWidth
              }
            )
            this.canvas.add(this.patternClass)
            break
          case 'dotted':   //绘制虚线
            this.patternClass = new fabric.Line(
              [
                this.downPoint.x, //虚线的开始坐标
                this.downPoint.y,
                this.downPoint.x, //第二个结束的坐标
                this.downPoint.y
              ],
              {
                fill: 'transparent',
                strokeDashArray: [10, 3], //设置为虚线,第一个值是实线的长度,第二个是虚线的长度
                stroke: this.borderColor,
                strokeWidth: this.borderWidth
              }
            )
            this.canvas.add(this.patternClass)
            break
          case 'circle':   //绘制圆形
            this.patternClass = new fabric.Circle({
              top: this.downPoint.y,
              left: this.downPoint.x,
              radius: 0, // 圆的半径
              fill: 'transparent',
              stroke: this.borderColor,
              strokeWidth: this.borderWidth
            })
            this.canvas.add(this.patternClass)
            break
          case 'triangle':   //绘制三角形
            this.patternClass = new fabric.Triangle({
              left: this.downPoint.x,
              top: this.downPoint.y,
              width: 0, //三角形底边的宽度
              height: 0, //底边到顶部角的高度
              fill: 'transparent',
              stroke: this.borderColor,
              strokeWidth: this.borderWidth
            })
            this.canvas.add(this.patternClass)
            break
          case 'broken':      //绘制折线
            const currentPoint = this.downPoint
            this.patternClass = new fabric.Polyline(
              [
                { x: currentPoint.x, y: currentPoint.y },
                { x: currentPoint.x, y: currentPoint.y }
              ],
              {
                fill: 'transparent',
                stroke: this.borderColor,
                strokeWidth:this.borderWidth,
                objectCaching: false
              }
            )
            this.canvas.add(this.patternClass)
            break;
          case 'arrow':   //绘制箭头
            let path = this.changePath(this.downPoint.x, this.downPoint.y, this.downPoint.x, this.downPoint.y, 17.5, 17.5)
            this.patternClass = new fabric.Path(path, {
              stroke: this.borderColor,
              fill: 'transparent',
              strokeWidth: this.borderWidth
            })
            this.canvas.add(this.patternClass)
            break
          case 'text':   //绘制文本
            this.patternClass = new fabric.Textbox('', {
              left: this.downPoint.x,
              top: this.downPoint.y,
              fontSize: 20,
              hasBorders: true, //修改的时候,是否显示文字所在区域
              width: 450,
              splitByGrapheme: true, //超过宽度自动换行
              fill: this.borderColor, //字体颜色
              borderColor: 'rgb(102,153,255,0.35)' //当元素被拖动或输入内容时的边框
            })
            this.canvas.add(this.patternClass)
            this.textObj = this.patternClass
            this.patternClass.enterEditing()
            break
        }
      },
      
      //判断上次的文字对象内容和编辑状态
      isTextObj(opt) {
        
        if (this.textObj !== '') {
          if (this.textObj.isEditing === true) {
            this.textObj.exitEditing()
            //将文字对象存在栈中
            this.backStack.push(this.textObj)
          }
          if (this.textObj.text === '') {
            this.canvas.remove(this.textObj) //空文本删除
            this.textObj = ''
          }
        }
        //判断当前点击的对象是不是文字
        if (opt.target) {
          this.textObj = opt.target
        }
      },

      //重新绘制图形
      patternFun(e) {    
      if (this.patternClass) {
        switch (this.clickMenubarName) {
          case 'rect':
            let rectW = Math.abs(this.downPoint.x - e.absolutePointer.x)
            let rectH = Math.abs(this.downPoint.y - e.absolutePointer.y)
            // 设置尺寸和所在位置
            this.patternClass.set('width', rectW)
            this.patternClass.set('height', rectH)
            this.patternClass.set('top', Math.min(e.absolutePointer.y, this.downPoint.y))
            this.patternClass.set('left', Math.min(e.absolutePointer.x, this.downPoint.x))
            // 刷新一下画布
            this.canvas.requestRenderAll()
            break
          case 'ellipse':
            let rx = Math.abs(this.downPoint.x - e.absolutePointer.x) / 2
            let ry = Math.abs(this.downPoint.y - e.absolutePointer.y) / 2
            let top = e.absolutePointer.y > this.downPoint.y ? this.downPoint.y : this.downPoint.y - ry * 2
            let left = e.absolutePointer.x > this.downPoint.x ? this.downPoint.x : this.downPoint.x - rx * 2
            // 设置椭圆尺寸和所在位置
            this.patternClass.set('rx', rx)
            this.patternClass.set('ry', ry)
            this.patternClass.set('top', top)
            this.patternClass.set('left', left)
            this.canvas.requestRenderAll()
            break
          case 'line':
            this.patternClass.set('x2', this.downPoint.x)
            this.patternClass.set('y2', this.downPoint.y)
            let newX = e.absolutePointer.x  
            let newY = e.absolutePointer.y
            this.patternClass.set({ x1: newX, y1: newY });  
            this.canvas.requestRenderAll()
            break
          case 'arrow':
            //先移除当前的图形,在重新生成新的
            this.canvas.remove(this.patternClass)
            let path = this.changePath(this.downPoint.x, this.downPoint.y, e.absolutePointer.x, e.absolutePointer.y, 17.5, 17.5)
            this.patternClass = new fabric.Path(path, {
              stroke: this.borderColor,
              fill: 'transparent',
              strokeWidth: this.borderWidth
            })
            this.canvas.add(this.patternClass)
            this.canvas.requestRenderAll()
            break
          case 'dotted':
            this.patternClass.set('x2', this.downPoint.x)
            this.patternClass.set('y2', this.downPoint.y)
            let new1X = e.absolutePointer.x  
            let new1Y = e.absolutePointer.y
            this.patternClass.set({ x1: new1X, y1: new1Y });  
            this.canvas.requestRenderAll()
            break
          case 'circle':
            let circleX = Math.abs(this.downPoint.x - e.absolutePointer.x) / 2
            let circleY = Math.abs(this.downPoint.y - e.absolutePointer.y) / 2
            let circleTop = e.absolutePointer.y > this.downPoint.y ? this.downPoint.y : this.downPoint.y - circleY * 2
            let circleLeft = e.absolutePointer.x > this.downPoint.x ? this.downPoint.x : this.downPoint.x - circleX * 2
            this.patternClass.set('radius', Math.max(circleX, circleY))
            this.patternClass.set('top', circleTop)
            this.patternClass.set('left', circleLeft)
            this.canvas.requestRenderAll()
            break
          case 'triangle':
            let triangleHeight = Math.abs(e.absolutePointer.y - this.downPoint.y)
            this.patternClass.set('width', Math.sqrt(Math.pow(triangleHeight, 2) + Math.pow(triangleHeight / 2.0, 2)))
            this.patternClass.set('height', triangleHeight)
            this.patternClass.set('top', Math.min(e.absolutePointer.y, this.downPoint.y))
            this.patternClass.set('left', Math.min(e.absolutePointer.x, this.downPoint.x))
            this.canvas.requestRenderAll()
            break
          case 'broken':
            //按下的时候只是修改最后一个节点的值
            const currentPoint = e.absolutePointer
            let points = this.patternClass.points
            points.push({
              x: currentPoint.x,
              y: currentPoint.y
            })
            this.canvas.requestRenderAll()
            break
        }
      }
      },

      //折线移动
      changePolylineBelt(e){
        if(this.patternClass){
          const currentPoint = e.absolutePointer
          let points = this.patternClass.points

          let newX = currentPoint.x
          let newY = currentPoint.y  

          points[points.length - 1].x = newX
          points[points.length - 1].y = newY

          this.canvas.requestRenderAll()

        }

      },

      // 完成折线绘制
      finishPolyline(e){
        const currentPoint = e.absolutePointer
        let points = this.patternClass.points
        points[points.length - 1].x = currentPoint.x
        points[points.length - 1].y = currentPoint.y
        points.pop()
        points.pop()
        // 按需添加自闭合代码
        // if (points[0].x != points[points.length - 1].x && points[0].y != points[points.length - 1].y) {
            // changepatternClass({ absolutePointer: { x: points[0].x, y: points[0].y } })
        // }
        this.canvas.remove(this.patternClass)
        if (points.length > 1) {
          this.patternClass = null
          this.patternClass = new fabric.Polyline(points, {
              fill: 'transparent',
              stroke: this.borderColor,
              strokeWidth:this.borderWidth,
              objectCaching: false
          })
          this.canvas.add(this.patternClass)
        }
        this.patternClass.style=this.clickMenubarName
        //将当前对象压入栈中
        this.backStack.push(this.patternClass)
        this.patternClass = null
        this.canvas.requestRenderAll()
      },

      //返回绘制箭头的路径值
      changePath(fromX, fromY, toX, toY, theta, headlen) {
        theta = typeof theta != 'undefined' ? theta : 30
        headlen = typeof theta != 'undefined' ? headlen : 10
        // 计算各角度和对应的P2,P3坐标
        let angle = (Math.atan2(fromY - toY, fromX - toX) * 180) / Math.PI,
          angle1 = ((angle + theta) * Math.PI) / 180,
          angle2 = ((angle - theta) * Math.PI) / 180,
          topX = headlen * Math.cos(angle1),
          topY = headlen * Math.sin(angle1),
          botX = headlen * Math.cos(angle2),
          botY = headlen * Math.sin(angle2)
        let arrowX = fromX - topX,
          arrowY = fromY - topY
        let path = ' M ' + fromX + ' ' + fromY
        path += ' L ' + toX + ' ' + toY
        arrowX = toX + topX
        arrowY = toY + topY
        path += ' M ' + arrowX + ' ' + arrowY
        path += ' L ' + toX + ' ' + toY
        arrowX = toX + botX
        arrowY = toY + botY
        path += ' L ' + arrowX + ' ' + arrowY
        return path
      },

      //菜单栏按钮点击事件
      clickEvent(value) {
        this.clickMenubarName = value
        if (value == 'select') {
          this.canvas.selection = true // 允许框选
          this.canvas.selectionColor = 'rgba(100, 100, 255, 0.3)' // 选框填充色:半透明的蓝色
          this.canvas.selectionBorderColor = 'rgba(255, 255, 255, 0.3)' // 选框边框颜色:半透明灰色
          this.canvas.skipTargetFind = false // 允许选中
          this.canvas.isDrawingMode = false //关闭自由绘画
        } else if (value == 'drawing') {
          this.canvas.selection = false // 去除框选
          this.canvas.skipTargetFind = true // 不允许选中
          this.canvas.isDrawingMode = true //开启自由绘画
        } else {
          this.canvas.selection = false  // 去除框选
          this.canvas.skipTargetFind = true  // 不允许选中
          this.canvas.isDrawingMode = false  //关闭自由绘画
        }
      },
  },

总结

到这里,基本图形的绘制就完成了,但是目前看来,还是不完善的,画布的操作没有加上,辅助绘制的一些操作也没有,后续会继续进行完善

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值