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


前言

本篇文章会开始进行图形的自由绘制,第二篇文章是绘制固定的图形,现在是在此基础上,进行自由的绘制,也是项目的开始阶段


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

一、自由绘制原理说明

自由绘制图形需要先选中要绘制的图形,然后在画布中监听鼠标,根据鼠标去绘制选择的图形。其中需要分为两类:

一类是矩形、线段、三角形、圆形等。这类图形只需要监听鼠标按下的地方,然后鼠标松开的地方,即可进行绘制。

一类是连续的折线,这类图形需要监听鼠标按下的每个地方,然后鼠标双击或者鼠标右键的地方为终点,进行绘制。

下面会分别进行绘制

二、自由绘制图形

1.框架搭建

1.1首先把基础框架搭建出来
我采用了element-ui这个UI库进行界面搭建,可直接安装,并引入

安装:

npm i element-ui -S

在main.js中引入:

import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

Vue.config.productionTip = false
Vue.use(ElementUI)

new Vue({
  render: h => h(App),
}).$mount('#app')

因为是演示,我这里采用的是全局引入的方式

1.2然后把绘制的框架搭建起来
操作栏:

    <!-- 操作栏 -->
    <el-radio-group v-model="clickMenubarName" size="mini">
      <el-radio border v-for="item in menubarFun" :key="item.id" @click="clickEvent(item.name)" :title="item.name" :label='item.name'>{{item.title}}</el-radio>
    </el-radio-group>

通过循环渲染:

      menubarFun: [
        {
          id: 1,
          name: 'select',
          title: '选择', //提示
        },
        {
          id: 2,
          name: 'drawing',
          title: '自由绘画',
        },
        {
          id: 3,
          name: 'line',
          title: '直线',
        },
        {
          id: 4,
          name: 'dotted',
          title: '虚线',
        },
        {
          id: 11,
          name: 'arrow',
          title: '箭头',
        },
        {
          id:9,
          name:'broken',
          title:'折线',
        },
        {
          id: 5,
          name: 'rect',
          title: '矩形',
        },
        {
          id: 6,
          name: 'circle',
          title: '圆形',
        },
        {
          id: 7,
          name: 'ellipse',
          title: '椭圆形',
        },
        {
          id: 8,
          name: 'triangle',
          title: '三角形',
        },
        {
          id: 10,
          name: 'text',
          title: '文本',
        }
      ],

效果如下:
在这里插入图片描述
1.3当选择不同的图形之后,就可以执行不同的绘制操作
需要定义菜单的点击事件:

      //菜单栏按钮点击事件
      clickEvent(value) {
      this.clickMenubarName = value
      if (value == 'select') {
        this.canvasObj.skipTargetFind = false // 允许选中
        this.canvasObj.isDrawingMode = false //关闭自由绘画
      } else if (value == 'drawing') {
        this.canvasObj.selection = false // 去除框选
        this.canvasObj.skipTargetFind = true // 不允许选中
        this.canvasObj.isDrawingMode = true //开启自由绘画
      } else {
        this.canvasObj.selection = false  // 去除框选
        this.canvasObj.skipTargetFind = true  // 不允许选中
        this.canvasObj.isDrawingMode = false  //关闭自由绘画
      }
    },

定义三类:

  • 点击选择按钮:不进行绘制
  • 点击自由绘制:可以在画布中进行自由的绘制
  • 点击其它的图形:可以进行图形绘制

效果如下:
在这里插入图片描述

1.4监听鼠标的事件,进行不同图形的绘制

      //创建图形
      drawing() {
        switch (this.clickMenubarName) {
          case 'rect':  //绘制矩形
            break
          case 'ellipse':   //绘制椭圆形
            break
          case 'line':    //绘制直线
            break
          case 'dotted':   //绘制虚线
            break
          case 'circle':   //绘制圆形
            break
          case 'triangle':   //绘制三角形
            break
          case 'broken':      //绘制折线
            break;
          case 'arrow':   //绘制箭头
            break
          case 'text':   //绘制文本
            break
        }
      },

1.5图形绘制的步骤
除了折线和文本外,其它基础图形的绘制步骤都是一样的,如下:

  1. 鼠标选中图形
  2. 鼠标在画布中按下,按下的时候记住按下的坐标
  3. 鼠标按下在画布中移动,每次移动需要重新获取坐标点,并且重新绘制当前的图形
  4. 当鼠标松开的时候,记住松开的时候的坐标,根据按下时的坐标和结束时的坐标进行图形绘制
  5. 然后把绘制好的图形放在栈中存储

折线和文本的稍微复杂一点,后面介绍

下面先以矩形的绘制进行说明,后续的其他图形的框架类似,只是图形绘制的方法不一样

1.绘制矩形

按照上述的步骤进行绘制
1.鼠标选中图形
2.鼠标在画布中按下
按下之后,记住按下的坐标,同时调用绘制图形的方法,进行图形绘制

        //鼠标按下
        'mouse:down': opt => {
          this.downPoint = opt.absolutePointer //记录下鼠标按下时的坐标
          this.drawing()  //绘制图形
        },

绘制图形的方法如下:

      //创建图形
      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':   //绘制椭圆形
            break
          case 'line':    //绘制直线
            break
          case 'dotted':   //绘制虚线
            break
          case 'circle':   //绘制圆形
            break
          case 'triangle':   //绘制三角形
            break
          case 'broken':      //绘制折线
            break;
          case 'arrow':   //绘制箭头
            break
          case 'text':   //绘制文本
            break
        }
      },

3.鼠标在画布中移动
鼠标在移动的时候,需要不断获取鼠标的坐标,并且不断的重新绘制图形

        //鼠标移动
        'mouse:move': opt => {
          this.patternFun(opt)  //重绘图形
        },

重绘图形的方法如下:

      //重新绘制图形
      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':
            break
          case 'line':
            break
          case 'arrow':
            break
          case 'dotted':
            break
          case 'circle':
            break
          case 'triangle':
            break
          case 'broken':
            break
        }
      }
    },

4.当鼠标松开的时候
鼠标松开之后,过滤掉一些杂的图形(即开始点和结束点一样的图形),然后把绘制好的图形放在栈中存储

        //鼠标抬起
        'mouse:up': opt => {
          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
          }
        },

这样,矩形的自由绘制就完成了,其中backStack里面会存储所有绘制的图形对象,看下效果:
在这里插入图片描述
完整代码:

<template>
  <div class="hello">
    <!-- 操作栏 -->
    <el-radio-group v-model="clickMenubarName"  @change="clickEvent" size="mini">
      <el-radio border v-for="item in menubarFun" :key="item.id" :title="item.name" :label='item.name'>{{item.title}}</el-radio>
    </el-radio-group>
    
    <br>

    <!-- 画布元素 -->
    <canvas width="1200" height="500" id="canvas-box" style="border: 1px solid #ccc;"></canvas>    
  </div>
</template>

<script>

import { fabric } from 'fabric'

export default {
  name: 'HelloWorld',
  data() {
    return {
      menubarFun: [
        {
          id: 1,
          name: 'select',
          title: '选择', //提示
        },
        {
          id: 2,
          name: 'drawing',
          title: '自由绘画',
        },
        {
          id: 3,
          name: 'line',
          title: '直线',
        },
        {
          id: 4,
          name: 'dotted',
          title: '虚线',
        },
        {
          id: 11,
          name: 'arrow',
          title: '箭头',
        },
        {
          id:9,
          name:'broken',
          title:'折线',
        },
        {
          id: 5,
          name: 'rect',
          title: '矩形',
        },
        {
          id: 6,
          name: 'circle',
          title: '圆形',
        },
        {
          id: 7,
          name: 'ellipse',
          title: '椭圆形',
        },
        {
          id: 8,
          name: 'triangle',
          title: '三角形',
        },
        {
          id: 10,
          name: 'text',
          title: '文本',
        }
      ],
      clickMenubarName: 'select', //当前点击的name
      downPoint: {}, //鼠标按下的点位置
      borderWidth: 4, //所有图形边框的宽度
      borderColor: '#000000', //所有图形边框的颜色
      backStack: [], //所有的绘画对象栈
      patternClass: null, //鼠标按下绘制的对象
    }
  },

  mounted(){
    this.init()
  },

  methods:{
    init(){
      this.canvas = new fabric.Canvas('canvas-box') // 这里传入的是canvas的id

      this.canvas.on({
        //鼠标按下
        'mouse:down': opt => {
          this.downPoint = opt.absolutePointer //记录下鼠标按下时的坐标
          this.drawing()  //绘制图形
        },

        //鼠标移动
        'mouse:move': opt => {
          this.patternFun(opt)  //重绘图形
        },

        //鼠标抬起
        'mouse:up': opt => {
          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
          }
        },

      })
    },

      //创建图形
      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':   //绘制椭圆形
            break
          case 'line':    //绘制直线
            break
          case 'dotted':   //绘制虚线
            break
          case 'circle':   //绘制圆形
            break
          case 'triangle':   //绘制三角形
            break
          case 'broken':      //绘制折线
            break;
          case 'arrow':   //绘制箭头
            break
          case 'text':   //绘制文本
            break
        }
      },

      //重新绘制图形
      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':
            break
          case 'line':
            break
          case 'arrow':
            break
          case 'dotted':
            break
          case 'circle':
            break
          case 'triangle':
            break
          case 'broken':
            break
        }
      }
    },

      //菜单栏按钮点击事件
      clickEvent(value) {
        console.log(value)
        this.clickMenubarName = value
        if (value == 'select') {
          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  //关闭自由绘画
        }
      },
  },
}
</script>

<style scoped lang="less">
.hello{
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction:column ;
  justify-content:center;
  align-items:center;
}

/deep/.el-radio{
  margin: 0;
}
</style>

后续的基础图形绘制的框架都是这个框架,即鼠标按下调用绘制的方法,鼠标移动就重新绘制图形,鼠标松开之后把绘制的图形存入栈中(把图形存入栈中的目的是为了序列化和反序列化,即重新加载渲染或者导出),不同的是绘制方法不一样,然后重新绘制的方法不一样

3.绘制直线

绘制方法:

          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 '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

4.绘制虚线

绘制方法:

          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 '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

5.绘制箭头

绘制方法:

          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 '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

箭头这里还需要一个方法,即获取箭头的路径的方法——changePath()。这个方法如下:

      //返回绘制箭头的路径值
      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
      },

6.绘制圆形

绘制方法:

          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 '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

7.绘制椭圆

绘制方法:

          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 '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

8.绘制三角形

绘制方法:

          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 '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

完整代码

完整代码如下(示例):

<template>
  <div class="hello">
    <!-- 操作栏 -->
    <el-radio-group v-model="clickMenubarName"  @change="clickEvent" size="mini">
      <el-radio border v-for="item in menubarFun" :key="item.id" :title="item.name" :label='item.name'>{{item.title}}</el-radio>
    </el-radio-group>
    
    <br>

    <!-- 画布元素 -->
    <canvas width="1200" height="500" id="canvas-box" style="border: 1px solid #ccc;"></canvas>    
  </div>
</template>

<script>

import { fabric } from 'fabric'

export default {
  name: 'HelloWorld',
  data() {
    return {
      menubarFun: [
        {
          id: 1,
          name: 'select',
          title: '选择', //提示
        },
        {
          id: 2,
          name: 'drawing',
          title: '自由绘画',
        },
        {
          id: 3,
          name: 'line',
          title: '直线',
        },
        {
          id: 4,
          name: 'dotted',
          title: '虚线',
        },
        {
          id: 11,
          name: 'arrow',
          title: '箭头',
        },
        {
          id:9,
          name:'broken',
          title:'折线',
        },
        {
          id: 5,
          name: 'rect',
          title: '矩形',
        },
        {
          id: 6,
          name: 'circle',
          title: '圆形',
        },
        {
          id: 7,
          name: 'ellipse',
          title: '椭圆形',
        },
        {
          id: 8,
          name: 'triangle',
          title: '三角形',
        },
        {
          id: 10,
          name: 'text',
          title: '文本',
        }
      ],
      clickMenubarName: 'select', //当前点击的name
      downPoint: {}, //鼠标按下的点位置
      borderWidth: 4, //所有图形边框的宽度
      borderColor: '#000000', //所有图形边框的颜色
      backStack: [], //所有的绘画对象栈
      patternClass: null, //鼠标按下绘制的对象
    }
  },

  mounted(){
    this.init()
  },

  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.drawing()  //绘制图形
        },

        //鼠标移动
        'mouse:move': opt => {
          this.patternFun(opt)  //重绘图形
        },

        //鼠标抬起
        'mouse:up': opt => {
          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
          }
        },

      })
    },

      //创建图形
      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':      //绘制折线
            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':   //绘制文本
            break
        }
      },

      //重新绘制图形
      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':
            break
        }
      }
    },

      //返回绘制箭头的路径值
      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  //关闭自由绘画
        }
      },
  },
}
</script>

<style scoped lang="less">
.hello{
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction:column ;
  justify-content:center;
  align-items:center;
}

/deep/.el-radio{
  margin: 0;
}
</style>

效果如下:
在这里插入图片描述


总结

本文主要介绍了基本图形的绘制方法,可以通过鼠标在画布上点击自由进行绘制,而不是固定的绘制,下一章会继续介绍文本和折线的自由绘制方法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值