用fabric.js实现一个比较LOW的钢笔工具

1,141 阅读1分钟

最近接到个需求,需要在图片上选择一块区域后,给里面填充颜色,这个就类似于ps里面的钢笔工具,可以参考下www.uupoop.com/ 里的钢笔工具

选择使用fabric.js来实现需求 ,对fabric.js不了解的同学可以看看这个中文文档github.com/Rookie-Bird…

实现步骤
1.实现添加点和路径,连接成图形
监听鼠标点击事件,这里的点使用circle对象实现,线和后面的图使用path路径对象来实现,这两个对象的基本介绍可参看github.com/Rookie-Bird…

 var path, str; // path是绘制的对象,str是其路径
 var pointPosition = [] // 点的位置坐标信息
 var pointArray = [] // 点的对象组合
 
canvas.on('mouse:down', function (e) {
	// addPoint添加点
        addPoint(e)
})

// 增加点,和路径,开始绘画
    function addPoint(e) {
      var id = new Date().getTime() + Math.random() // id用来识别每个点
      var circle = new fabric.Circle({
        radius: 3,
        fill: '#1a80ff',
        left: e.absolutePointer.x,
        top: e.absolutePointer.y,
        selectable: false,
        hasBorders: false,
        hasControls: false,
        originX: 'center',
        originY: 'center',
        id: id
      })
      canvas.add(circle)
      pointPosition.push({
        x: e.absolutePointer.x,
        y: e.absolutePointer.y
      })
      // pointPosition.length == 1 说明是第一个点
      // 这里用fabric.js里的path对象来绘制我们需要的图形对象
      // 此时往路径里面添加'M','M'的意思是开始绘画,后面的两个数字是开始点的x,y的坐标
      if (pointPosition.length == 1) {
        str = 'M' + pointPosition[0].x + ' ' + pointPosition[0].y
        path = new fabric.Path(str)
        path.set({
          fill: 'red',

          // opacity: 0.5,
          selectable: false,
          hasBorders: false,
          hasControls: false,
          evented: false
        })
        canvas.add(path);
        pointArray.push(circle)
      }
      // 增加第二个点时再执行下面的逻辑,这里开始绘制直线
      if (pointPosition.length <= 1) return
      var length = pointPosition.length - 1
      canvas.remove(path) // 先移除之前的对象
      // L在path里面就是线段的意思
      str = str + 'L' + pointPosition[length].x + ' ' + pointPosition[length].y // 路径拼接
      path = new fabric.Path(str)
      path.set({
        fill: '#7c4529',
        // opacity: 0.5,
        strokeWidth: 1.5,
        stroke: '#1a80ff',
        selectable: true,
        hasBorders: false,
        hasControls: false,
        evented: false

      })
      canvas.add(path); // 重新添加

      canvas.renderAll()
      pointArray.push(circle)
    }
   

效果 现在我们将这个图形封闭一下,就是当最后一个点与起始点重合的时候就说明图形已经画完了

canvas.on('mouse:down', function (e) {
     if(e.target && pointArray[0] && e.target.id == pointArray[0].id) { // 目标点和第一个点id相等时,说明绘画动作已经完成,此时开始最后一步绘画
        finishDrawing(e)
        
      } else {
       // canvas.getActiveObject() 为true说明这时候你选中了其它对象,这个时候不进行绘画
        if (canvas.getActiveObject()) return
        addPoint(e)
     
      }

    });
    
    function finishDrawing(e) {
      canvas.remove(path)
      // 最后一个点的坐标就是第一个点的坐标
      pointPosition.push({
        x: pointPosition[0].x,
        y: pointPosition[0].y
      })
      // 给路径加'z','z'说明整个path路径已经完结
      str = str + 'z'
      path = new fabric.Path(str)
      path.set({
        fill: '#7c4529',
        // opacity: 0.5,
        strokeWidth: 1.5,
        stroke: '#1a80ff',
        // selectable: true,
        // hasBorders: true,
        // hasControls: true,
        // evented: true

      })
      canvas.add(path);
      canvas.renderAll()
    }

图形封闭完之后,再点击的话就是开始下一次绘图了,这里再次点击的话,我们将之前保存的点的坐标和路径全部清除,对鼠标按下事件在做些处理

 canvas.on('mouse:down', function (e) {
    
      // 如果str包含z,也就是说路径里面有z,说明图形已经绘画完毕了,将之前的所有点和点的位置信息全部清空
      if (str && str.indexOf('z') !== -1) {
        removePoint()
        addPoint(e)
      } else if (e.target && pointArray[0] && e.target.id == pointArray[0]
        .id) { // 目标点和第一个点id相等时,说明绘画动作已经完成,此时开始最后一步绘画
        finishDrawing(e)

      } else {
        if (canvas.getActiveObject()) return
        addPoint(e)
      }
    });
  // 移除点 
    function removePoint() {
      for (let item of pointArray) {
        canvas.remove(item)
      }
      str = ''
      pointArray = []
      pointPosition = []
      path.set({
        stroke: null, // 路径完成后讲path的边框去掉
      })
      canvas.renderAll()
    }

效果

2.添加鼠标按下移动时,增加跟随鼠标转动的点和线,这也是为了完成别赛尔曲线的绘制
仿照UUPOOP的实现 首先,在鼠标按下时,监听鼠标移动事件(这里要注意,鼠标弹起时,一定得取消鼠标移动的监听事件,否则会造成重复监听多个鼠标移动事件,造成页面卡顿)

 canvas.on('mouse:down', function (e) {
      canvas.on('mouse:move', startDrawing);
    });
    
    canvas.on('mouse:up', function (e) {
     
      // 鼠标弹起时,取消监听移动事件
      canvas.off('mouse:move', startDrawing)
    });
    // 开始绘制跟随鼠标移动的点和线,具体请参看完整代码
    var startDrawing = function (e) {
      dragMousePoint(e)
    }

3.绘制贝赛尔曲线的绘制
这里我们使用path自带的Q,也就是二阶贝塞尔曲线
可以看下这个动态的二阶贝塞尔曲线事例blogs.sitepointstatic.com/examples/te… 它需要三个点,一个是起始点'M' + pointPosition[length - 1].x + ' ' + pointPosition[length - 1].y 一个是它的控制点 x,y,还有一个就是结束点 这里我们新建一个名为besairObj的path对象来展示贝塞尔曲线的变化

// 跟随鼠标改动的贝塞尔曲线
    function BeSaiEr(e, length) {
    // x,y是跟随鼠标移动的线段2的末尾点的坐标
      // pointPosition[length].x 鼠标移动事件的起始点
     besair = 'M' + pointPosition[length - 1].x + ' ' + pointPosition[length - 1].y +  ' Q ' + x + ' ' + y + ' ' + pointPosition[length ].x + ' ' + pointPosition[length ].y
      // besair = ' Q ' + x + ' ' + y + ' ' + pointPosition[length-1].x + ' ' + pointPosition[length-1].y 
      // besair = ' Q ' + x + ' ' + y + ' ' + pointPosition[length].x + ' ' + pointPosition[length].y
      if (besairObj) {
        canvas.remove(besairObj)
      }
      besairObj = new fabric.Path(besair)
      besairObj.set({
        fill: '#7c4529',
        // opacity: 0.5,
        strokeWidth: 1.5,
        stroke: '#1a80ff',
        selectable: true,
        hasBorders: false,
        hasControls: false,
        evented: false

      })
      canvas.add(besairObj)
      // canvas.add(path);

      besair = ' Q ' + x + ' ' + y + ' ' + pointPosition[length].x + ' ' + pointPosition[length].y
    }

这里修改一下鼠标弹起事件,拼接路径

canvas.on('mouse:up', function (e) {
      if (besair) {
        // 在移动事件中,每次都将最后一个L的数据排除掉,避免bug
        if (str.lastIndexOf('L') !== -1) {
          str = str.substring(0, str.lastIndexOf('L'))
        }
        // 如果有z这说明本次绘图已经结束,不去掉z会造成图形失真,有bug
        if (str.indexOf('z') !== -1) {
          str = str.substring(0, str.indexOf('z'))
          besair = besair + 'z'
        }
        // 鼠标弹起时,如果besair有值,则与str合并,并将其置为空字符串
        str = str + besair
        console.log(str)
        besair = ''
      }
      // 鼠标弹起时,取消监听移动事件
      canvas.off('mouse:move', startDrawing)
    });
	
完整代码
<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title></title>
  <style>
    * {
      padding: 0;
      margin: 0;
    }

    .backgrounds {
      display: flex;
    }

    .backgrounds img {
      width: 200px;
      height: 200px;
    }

    #CanvasContainer {
      width: 270px;
      height: 519px;
      margin-left: 15px;
    }

    #Canvas {
      overflow: hidden;
    }

    .tag {
      position: absolute;
      z-index: 15;
      padding: 0 5px;
      min-width: 48px;
      height: 16px;
      line-height: 16px;
      text-align: center;
      font-size: 12px;
      color: #505050;
      border: 1px solid #fff;
      background: hsla(0, 0%, 86.3%, .8);
      border-radius: 10px;
      -webkit-border-radius: 10px;
      display: none;
    }

    .pointer {
      position: absolute;
      width: 10px;
      height: 10px;
      border: 1px solid rgb(148, 148, 226);
    }
  </style>
</head>

<body>
  <div id="Backgrounds" class="backgrounds">
    <img src="./鞋1.jpg" alt="" id="img1" />
    <img src="./鞋2.png" alt="" id="img2" />
    <img src="./鞋3.jpg" alt="" id="img3" />
  </div>
  <div class="container" style="position: relative;">
    <div id="CanvasContainer" style="width: 800px;height: 800px;border: 1px solid #ccc;">
      <canvas id="Canvas" width="800" height="800"></canvas>
    </div>
    <div class="tag" id="tag">3</div>
    <img src="./DIY配色封面图-男.jpg" id="deleteBtn"
      style="position:absolute;top: 0px;left: 0px;cursor:pointer;width:20px;height:20px;display: none;" />
  </div>
  <button onclick="downloadFabric(canvas, new Date().getTime())">导出</button>
  <button onclick="unload()">离开</button>
  <button onclick="cancelDraw()">手绘模式</button>
  <button onclick="fillColor()">填充颜色</button>
  <button onclick="huatu()">绘画</button>
  <script src="https://cdn.bootcdn.net/ajax/libs/fabric.js/4.2.0/fabric.js"></script>
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
  <script>
    var canvas = new fabric.Canvas('Canvas');
    $(document).ready(function () {
      $("#Backgrounds img").click(function () {
        var getId = $(this).attr("id");

        var imgElement = document.getElementById(getId);
        var imgInstance = new fabric.Image(imgElement, {
          left: 0,
          top: 0
        });
        imgInstance.set({
          scaleX: 0.3,
          scaleY: 0.3,
          transparentCorners: false,
          cornerColor: 'black',
          cornerStrokeColor: 'black',
          borderColor: '#686666',
          cornerSize: 12,
          padding: 10,
          cornerStyle: 'circle',
          borderDashArray: [3, 3],
          tr: './aaaaa.jpg'
        });

        canvas.add(imgInstance);
      });
    });


    // 设置画布背景
    fabric.Image.fromURL('./aaaaa.jpg', (img) => {
      img.set({
        // 通过scale来设置图片大小,这里设置和画布一样大
        scaleX: canvas.width / img.width,
        scaleY: canvas.height / img.height,
      });
      // 设置背景
      canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas));
      canvas.renderAll();
    });

    // 导出下载图片
    function download(url, name) {
      $('<a>').attr({
        href: url,
        download: name
      })[0].click();
    }

    function downloadFabric(canvas, name) {
      // 导出合并后的图片
      download(canvas.toDataURL(), name + '.png');
      // 导出单独的图片
      // download(canvas._objects[0].toDataURL(),name+'.png');
    }

    var tag = document.getElementById('tag');
    var zoom, zoomPoint;

    canvas.on({
      // 鼠标滚动缩放
      "mouse:wheel": (e) => {
        zoom = (event.deltaY > 0 ? -0.1 : 0.1) + canvas.getZoom();
        zoom = Math.max(0.1, zoom); //最小为原来的1/10
        zoom = Math.min(3, zoom); //最大是原来的3倍
        //	zoomPoint = new fabric.Point(e.pointer.x, e.pointer.y);
        zoomPoint = new fabric.Point(400, 400); // 中心点
        canvas.zoomToPoint(zoomPoint, zoom);
      },
      // 鼠标旋转
      "object:rotating": (e) => {
        tag.style.display = 'block';
        var offsetX = e.e.offsetX;
        var offsetY = e.e.offsetY;
        tag.style.left = offsetX + 30 + 'px'; // 离鼠标太近,可能会出现抖动,闪现
        tag.style.top = offsetY + 30 + 'px';
      },
      "object:rotated": (e) => {
        tag.style.display = 'none';
      },
    })

    // 离开页面,保存当前的画布信息
    function unload() {
      // 导出当前画布信息
      const currState = canvas.toJSON();
      console.log(currState);
      sessionStorage.setItem('img', JSON.stringify(currState));
      sessionStorage.setItem('zoomObj', JSON.stringify({
        zm: zoom,
        zmpoint: zoomPoint
      }));
    }
    let draw = false

    function cancelDraw() {
      draw = !draw
      canvas.isDrawingMode = draw

    }


    function huatu() {
      drawing = !drawing
      if (path) {
        removePoint()
      }
    }

    function fillColor() {
      var e = canvas.getActiveObject()
      console.log(e)

      fabric.util.loadImage('./DIY配色封面图-男.jpg', function (img) {
        e.set('fill', new fabric.Pattern({
          source: img,
          opacity: 1
        }));
        canvas.renderAll();
      });

      e.bringForward()
      // e.setBackgroundImage = './鞋3.jpg'


      canvas.renderAll()
    }
    var selectd = null


    // 刷新,恢复之前的画布信息
    var sessionImg = sessionStorage.getItem('img');
    var lastState = sessionImg ? JSON.parse(sessionImg) : '';
    var zoomObj = sessionStorage.getItem('zoomObj') ? JSON.parse(sessionStorage.getItem('zoomObj')) : '';

    // 加载画布信息
    canvas.loadFromJSON(lastState, () => {
      // 设置缩放点
      zoomPoint = zoomObj.zmpoint;
      zoom = zoomObj.zm;
      canvas.zoomToPoint({
        x: zoomPoint.x,
        y: zoomPoint.y
      }, zoom);

      // 给每一个图层设置边框圆角样式  刷新后重绘,需要重新设置之前的一些样式
      var objects = canvas._objects;
      if (objects.length > 0) {
        objects.map(item => {
          item.set({
            transparentCorners: false,
            cornerColor: 'black',
            cornerStrokeColor: 'black',
            borderColor: '#686666',
            cornerSize: 12,
            padding: 10,
            cornerStyle: 'circle',
            borderDashArray: [3, 3],
          })
        })
      }
      // 重绘
      canvas.renderAll();
    });

    // 删除某个图层
    var deleteBtn = document.getElementById('deleteBtn');

    function addDeleteBtn(x, y) {
      console.log(x, y)
      deleteBtn.style.display = 'none';
      deleteBtn.style.left = x + 'px';
      deleteBtn.style.top = y + 'px';
      deleteBtn.style.display = 'block';
    }

    var path, str; // path是绘制的对象,str是其路径
    var pointPosition = [] // 点的位置坐标信息
    var pointArray = [] // 点的对象组合
    var drawing = false // 控制是否开始自由绘画
    var mouseMoving = false // 用于判断鼠标是否在移动
    var isMoving = false // 用于判断是否进入移动鼠标事件
    var mousePoint1; // 鼠标拖动的点1
    var mousePoint2; // 鼠标拖动的点2
    var mouseLine1; // 鼠标拖动的线1
    var mouseLine2; // 鼠标拖动的线2
    var besair = '' // 贝塞尔曲线的坐标
    // x , y 是线段1的镜象线的坐标值,也就是线段2的末尾值  
    var x;
    var y;

    var besairObj; // 贝塞尔曲线的对象
    // 增加点,和路径,开始绘画
    function addPoint(e) {
      var id = new Date().getTime() + Math.random()
      var circle = new fabric.Circle({
        radius: 3,
        fill: '#1a80ff',
        left: e.absolutePointer.x,
        top: e.absolutePointer.y,
        selectable: false,
        hasBorders: false,
        hasControls: false,
        originX: 'center',
        originY: 'center',
        id: id
      })
      canvas.add(circle)
      pointPosition.push({
        x: e.absolutePointer.x,
        y: e.absolutePointer.y
      })
      if (pointPosition.length == 1) {
        str = 'M' + pointPosition[0].x + ' ' + pointPosition[0].y
        path = new fabric.Path(str)
        path.set({
          fill: 'red',

          // opacity: 0.5,
          selectable: false,
          hasBorders: false,
          hasControls: false,
          evented: false
        })
        canvas.add(path);
        pointArray.push(circle)
      }
      pointArray.push(circle)
      // 增加第二个点时再执行下面的逻辑
      if (pointPosition.length <= 1) return
      var length = pointPosition.length - 1
      canvas.remove(path)
      // str = str + 'L' + pointPosition[length].x + ' ' + pointPosition[length].y + 'z'
      str = str + 'L' + pointPosition[length].x + ' ' + pointPosition[length].y
      path = new fabric.Path(str)
      path.set({
        fill: '#7c4529',
        // opacity: 0.5,
        strokeWidth: 1.5,
        stroke: '#1a80ff',
        selectable: true,
        hasBorders: false,
        hasControls: false,
        evented: false

      })
      canvas.add(path);

      canvas.renderAll()
     
    }
    // 整个图形成型,包括圆点
    function finishDrawing(e) {
      canvas.remove(path)
      // 最后一个点的坐标就是第一个点的坐标
      pointPosition.push({
        x: pointPosition[0].x,
        y: pointPosition[0].y
      })
      str = str + 'z'
      path = new fabric.Path(str)
      path.set({
        fill: '#7c4529',
        // opacity: 0.5,
        strokeWidth: 1.5,
        stroke: '#1a80ff',
        // selectable: true,
        // hasBorders: true,
        // hasControls: true,
        // evented: true

      })
      canvas.add(path);
      canvas.renderAll()
    }

    // 移除点 
    function removePoint() {
      for (let item of pointArray) {
        canvas.remove(item)
      }
      console.log(222222)
      // 结束绘画时,如果贝塞尔曲线的对象存在,则将其移除,并置空
      if (besairObj) {
        canvas.remove(besairObj)
        besairObj = null
        if (path) canvas.remove(path)
        path = new fabric.Path(str)
        path.set({
          fill: '#7c4529',
          strokeWidth: 1.5,
          stroke: '#1a80ff',
        })
        canvas.add(path);
      }
      str = ''
      pointArray = []
      pointPosition = []
      path.set({
        stroke: null,
      })
      canvas.renderAll()
    }

    // 跟随鼠标拖动的点和线
    function dragMousePoint(e) {
      var length = pointPosition.length - 1

      mouseMoveLine1(e, length)
      mouseMoveLine2(e, length)
      mouseMovePoint(e)
      BeSaiEr(e, length)
      // throttle(BeSaiEr(e, length),1000)


      canvas.renderAll()
    }
    // 跟随鼠标走动的两个点
    function mouseMovePoint(e) {
      // var x2 = (y  / Math.sqrt(Math.pow(x,2) + Math.pow(y,2)) ) * 1.5
      // var y2 = (x  / Math.sqrt(Math.pow(x,2) + Math.pow(y,2)) ) * 1.5
      if (!mousePoint1) {
        mousePoint1 = new fabric.Circle({
          radius: 4,
          fill: 'white',
          left: e.absolutePointer.x,
          top: e.absolutePointer.y,
          selectable: false,
          hasBorders: false,
          hasControls: false,
          originX: 'center',
          originY: 'center',
          strokeWidth: 0.8,
          stroke: '#1a80ff',
          // id: id
        })

        mousePoint2 = new fabric.Circle({
          radius: 4,
          fill: 'white',
          left: x,
          top: y,
          selectable: false,
          hasBorders: false,
          hasControls: false,
          originX: 'center',
          originY: 'center',
          strokeWidth: 0.8,
          stroke: '#1a80ff',

          // id: id
        })
        canvas.add(mousePoint2)
        canvas.add(mousePoint1)
      }
      mousePoint1.set({
        left: e.absolutePointer.x,
        top: e.absolutePointer.y
      })
      mousePoint2.set({
        left: x,
        top: y
      })
    }

    // 跟随鼠标走动的线1
    function mouseMoveLine1(e, length) {
      if (mouseLine1) canvas.remove(mouseLine1)
      canvas.selection = false
      // new fabric.Line(x1,y1,x2,y2)  x1,y1是起始点,x2,y2是结束点
      mouseLine1 = new fabric.Line([pointPosition[length].x, pointPosition[length].y,
        e.absolutePointer.x, e.absolutePointer.y
      ], {
        strokeWidth: 2,
        fill: '#999999',
        stroke: '#999999',
        class: 'line',
        originX: 'center',
        originY: 'center',
        selectable: false,
        hasBorders: false,
        hasControls: false,
        evented: false
      });
      canvas.add(mouseLine1)
    }
    // 跟随鼠标走动的线2
    function mouseMoveLine2(e, length) {
      // 鼠标移动点,也是线段的结束点
      var moveX = e.absolutePointer.x
      var moveY = e.absolutePointer.y
      // 线段的起始点
      var originX = pointPosition[length].x
      var originY = pointPosition[length].y

      if (mouseLine2) canvas.remove(mouseLine2)

      // x , y 是线段1的镜象线的坐标值,也就是线段2的末尾值  至于为什么x的值为x = 2 * originX - moveX,画个坐标出来就清楚了
      x = 2 * originX - moveX
      y = 2 * originY - moveY
      canvas.selection = false
      mouseLine2 = new fabric.Line([originX, originY,
        x, y
      ], {
        strokeWidth: 2,
        fill: '#999999',
        stroke: '#999999',
        class: 'line',
        originX: 'center',
        originY: 'center',
        selectable: false,
        hasBorders: false,
        hasControls: false,
        evented: false
      });
      canvas.add(mouseLine2)
    }


    // 跟随鼠标改动的贝塞尔曲线
    function BeSaiEr(e, length) {
      // pointPosition[length].x 鼠标移动事件的起始点
      besair = 'M' + pointPosition[length - 1].x + ' ' + pointPosition[length - 1].y +  ' Q ' + x + ' ' + y + ' ' + pointPosition[length ].x + ' ' + pointPosition[
          length ].y
      // besair = ' Q ' + x + ' ' + y + ' ' + pointPosition[length-1].x + ' ' + pointPosition[length-1].y 
      // besair = ' Q ' + x + ' ' + y + ' ' + pointPosition[length].x + ' ' + pointPosition[length].y
      if (besairObj) {
        canvas.remove(besairObj)
      }
      besairObj = new fabric.Path(besair)
      besairObj.set({
        fill: '#7c4529',
        // opacity: 0.5,
        strokeWidth: 1.5,
        stroke: '#1a80ff',
        selectable: true,
        hasBorders: false,
        hasControls: false,
        evented: false

      })
      canvas.add(besairObj)
      besair = ' Q ' + x + ' ' + y + ' ' + pointPosition[length].x + ' ' + pointPosition[length].y
    }

    canvas.on('selection:created', function (e) {
      selectd = e
      console.log(canvas.getZoom())
      // 第一次触发了,但是不变
      // setTimeout(()=>{addDeleteBtn(e.target.oCoords.tr.x , e.target.oCoords.tr.y )},0);
      // addDeleteBtn(e.target.lineCoords.tr.x, e.target.lineCoords.tr.y);
    });
    canvas.on('selection:updated', function (e) {
      // console.log(e);
      // addDeleteBtn(e.target.lineCoords.tr.x, e.target.lineCoords.tr.y);
    });

    canvas.on('mouse:down', function (e) {
      if (!canvas.getActiveObject()) {
        deleteBtn.style.display = 'none';
      }
      // 如果str包含z,也就是说路径里面有z,说明图形已经绘画完毕了,将之前的所有点和点的位置信息全部清空
      if (str && str.indexOf('z') !== -1) {
        removePoint()
        addPoint(e)
      } else if (e.target && pointArray[0] && e.target.id == pointArray[0].id) { // 目标点和第一个点id相等时,说明绘画动作已经完成,此时开始最后一步绘画
        finishDrawing(e)
       
      } else if (drawing) {
        if (canvas.getActiveObject()) return
        addPoint(e)
      
      }

      // 只有开启绘画,才进行鼠标拖动
      if (!drawing) return
      canvas.on('mouse:move', startDrawing);

    });
    var startDrawing = function (e) {
      
      dragMousePoint(e)
    }

    canvas.on('mouse:up', function (e) {
      
      canvas.selection = true

      if (besair) {
        // 在移动事件中,每次都将最后一个L的数据排除掉,避免bug
        if (str.lastIndexOf('L') !== -1) {
          str = str.substring(0, str.lastIndexOf('L'))
        }
        // 如果有z这说明本次绘图已经结束,不去掉z会造成图形失真,有bug
        if (str.indexOf('z') !== -1) {
          str = str.substring(0, str.indexOf('z'))
          besair = besair + 'z'
        }
        // 鼠标弹起时,如果besair有值,则与str合并,并将其置为空字符串
        str = str + besair
        console.log(str)
        besair = ''
      }
      // 鼠标弹起时,取消监听移动事件
      canvas.off('mouse:move', startDrawing)
    });

    canvas.on('object:modified', function (e) {
      // addDeleteBtn(e.target.oCoords.tr.x, e.target.oCoords.tr.y);
    });
    canvas.on('object:scaling', function (e) {
      // $(".deleteBtn").remove(); 
      deleteBtn.style.display = 'none';
    });
    canvas.on('object:moving', function (e) {
      console.log(e.target.aCoords);
      //			    $(".deleteBtn").remove(); 
      deleteBtn.style.display = 'none';
    });
    canvas.on('object:rotating', function (e) {
      // $(".deleteBtn").remove(); 
      deleteBtn.style.display = 'none';
      // addDeleteBtn(e.target.oCoords.tr.x, e.target.oCoords.tr.y);
    });
    canvas.on('mouse:wheel', function (e) {
      console.log(e)
      console.log(canvas.getZoom())
      deleteBtn.style.display = 'none';
      // addDeleteBtn(e.target.oCoords.tr.x, e.target.oCoords.tr.y);
    })
    $(document).on('click', "#deleteBtn", function () {
      if (canvas.getActiveObject()) {
        canvas.remove(canvas.getActiveObject());
        //			        $(".deleteBtn").remove();
        deleteBtn.style.display = 'none';
      }
    });
  </script>
</body>

</html>

说一下这个代码怎么跑起来:
1.启动http-server服务器,否则会报图片跨域的错误
2.将代码中的图片地址换成你自己的
3.点击绘画按钮,即可开始绘画,进入到钢笔工具