【问题标题】:Draw two head arrows in fabric.js在fabric.js中画两个箭头
【发布时间】:2021-07-19 19:45:03
【问题描述】:

我是 fabric.js 的新手,对浏览器画布 API 没有太多经验,所以我很感激有人提供的所有帮助。

要达到的目标是用鼠标箭头在3种不同的模式下绘制:

  1. 带 2 个头
  2. 一个头
  3. 完全没有头(只是一条简单的线)

有一个很好的例子——但只有一个“提示”。

还有一个更高级的主题可能是:在选择一个已经 创建箭头,以更改它(例如,单击一个按钮并将模式从一个带头箭头更改为两个)。

  _render: function(ctx) {
    this.callSuper('_render', ctx);

    // do not render if width/height are zeros or object is not visible
    if (this.width === 0 || this.height === 0 || !this.visible) return;

    ctx.save();

    var xDiff = this.x2 - this.x1;
    var yDiff = this.y2 - this.y1;
    var angle = Math.atan2(yDiff, xDiff);
    ctx.translate(xDiff / 2, yDiff / 2);
    ctx.rotate(angle);
    ctx.beginPath();
    //move 10px in front of line to start the arrow so it does not have the square line end showing in front (0,0)
    ctx.moveTo(10, 0);
    ctx.lineTo(-20, 15);
    ctx.lineTo(-20, -15);
    ctx.closePath();
    ctx.fillStyle = this.stroke;
    ctx.fill();

    ctx.restore();
  }

可能会更改此特定部分以代替添加另一个箭头:<--->

单头工作 JFiddle 的链接:Fiddle

提前感谢您的帮助。 万事如意!

【问题讨论】:

    标签: fabricjs


    【解决方案1】:

    将上下文翻译到线的两个端点,然后旋转以绘制箭头。

    演示

    // Extended fabric line class
    fabric.LineArrow = fabric.util.createClass(fabric.Line, {
    
      type: 'lineArrow',
    
      initialize: function(element, options) {
        options || (options = {});
        this.callSuper('initialize', element, options);
      },
    
      toObject: function() {
        return fabric.util.object.extend(this.callSuper('toObject'));
      },
    
      _render: function(ctx) {
        this.ctx = ctx;
        this.callSuper('_render', ctx);
        let p = this.calcLinePoints();
        let xDiff = this.x2 - this.x1;
        let yDiff = this.y2 - this.y1;
        let angle = Math.atan2(yDiff, xDiff);
        this.drawArrow(angle, p.x2, p.y2);
        ctx.save();
        xDiff = -this.x2 + this.x1;
        yDiff = -this.y2 + this.y1;
        angle = Math.atan2(yDiff, xDiff);
        this.drawArrow(angle, p.x1, p.y1);
      },
    
      drawArrow: function(angle, xPos, yPos) {
        this.ctx.save();
        this.ctx.translate(xPos, yPos);
        this.ctx.rotate(angle);
        this.ctx.beginPath();
        // Move 5px in front of line to start the arrow so it does not have the square line end showing in front (0,0)
        this.ctx.moveTo(10, 0);
        this.ctx.lineTo(-15, 15);
        this.ctx.lineTo(-15, -15);
        this.ctx.closePath();
        this.ctx.fillStyle = this.stroke;
        this.ctx.fill();
        this.ctx.restore();
      }
    });
    
    
    
    fabric.LineArrow.fromObject = function(object, callback) {
      callback && callback(new fabric.LineArrow([object.x1, object.y1, object.x2, object.y2], object));
    };
    
    fabric.LineArrow.async = true;
    
    
    var Arrow = (function() {
      function Arrow(canvas) {
        this.canvas = canvas;
        this.className = 'Arrow';
        this.isDrawing = false;
        this.bindEvents();
      }
    
      Arrow.prototype.bindEvents = function() {
        var inst = this;
        inst.canvas.on('mouse:down', function(o) {
          inst.onMouseDown(o);
        });
        inst.canvas.on('mouse:move', function(o) {
          inst.onMouseMove(o);
        });
        inst.canvas.on('mouse:up', function(o) {
          inst.onMouseUp(o);
        });
        inst.canvas.on('object:moving', function(o) {
          inst.disable();
        })
      }
    
      Arrow.prototype.onMouseUp = function(o) {
        var inst = this;
        this.line.set({
          dirty: true,
          objectCaching: true
        });
        inst.canvas.renderAll();
        inst.disable();
      };
    
      Arrow.prototype.onMouseMove = function(o) {
        var inst = this;
        if (!inst.isEnable()) {
          return;
        }
    
        var pointer = inst.canvas.getPointer(o.e);
        var activeObj = inst.canvas.getActiveObject();
        activeObj.set({
          x2: pointer.x,
          y2: pointer.y
        });
        activeObj.setCoords();
        inst.canvas.renderAll();
      };
    
      Arrow.prototype.onMouseDown = function(o) {
        var inst = this;
        inst.enable();
        var pointer = inst.canvas.getPointer(o.e);
    
        var points = [pointer.x, pointer.y, pointer.x, pointer.y];
        this.line = new fabric.LineArrow(points, {
          strokeWidth: 5,
          fill: 'red',
          stroke: 'red',
          originX: 'center',
          originY: 'center',
          hasBorders: false,
          hasControls: false,
          objectCaching: false,
          perPixelTargetFind: true
        });
    
        inst.canvas.add(this.line).setActiveObject(this.line);
      };
    
      Arrow.prototype.isEnable = function() {
        return this.isDrawing;
      }
    
      Arrow.prototype.enable = function() {
        this.isDrawing = true;
      }
    
      Arrow.prototype.disable = function() {
        this.isDrawing = false;
      }
    
      return Arrow;
    }());
    
    var canvas = new fabric.Canvas('canvas', {
      selection: false
    });
    var arrow = new Arrow(canvas);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.17/fabric.min.js"></script>
     Please draw arrow here
    
    <div id="canvasContainer">
      <canvas id="canvas" width="400" height="400" style="border: solid 1px"></canvas>
    </div>

    【讨论】:

      【解决方案2】:

      为了做简单的线,一个头或两个头的箭头,你需要一个自定义选项的路径。我使用自定义选项更新了@Durga 代码。我用过和数组:heads: [1,1]。头的可用选项可以是 0 或 1。0 - 无头,1 - 有头。因此,在这种情况下,您可以控制显示哪个头:左、右、两者或不显示:

      // Extended fabric line class
      fabric.LineArrow = fabric.util.createClass(fabric.Line, {
      
        type: 'lineArrow',
      
        initialize: function(element, options) {
          options || (options = {});
          this.callSuper('initialize', element, options);
        },
      
        toObject: function() {
          return fabric.util.object.extend(this.callSuper('toObject'));
        },
      
        _render: function(ctx) {
          this.ctx = ctx;
          this.callSuper('_render', ctx);
          let p = this.calcLinePoints();
          let xDiff = this.x2 - this.x1;
          let yDiff = this.y2 - this.y1;
          let angle = Math.atan2(yDiff, xDiff);
          this.drawArrow(angle, p.x2, p.y2, this.heads[0]);
          ctx.save();
          xDiff = -this.x2 + this.x1;
          yDiff = -this.y2 + this.y1;
          angle = Math.atan2(yDiff, xDiff);
          this.drawArrow(angle, p.x1, p.y1,this.heads[1]);
        },
      
        drawArrow: function(angle, xPos, yPos, head) {
          this.ctx.save();
         
          if (head) {
              this.ctx.translate(xPos, yPos);
              this.ctx.rotate(angle);
            this.ctx.beginPath();
            // Move 5px in front of line to start the arrow so it does not have the square line end showing in front (0,0)
            this.ctx.moveTo(10, 0);
            this.ctx.lineTo(-15, 15);
            this.ctx.lineTo(-15, -15);
            this.ctx.closePath();
          }
          
          this.ctx.fillStyle = this.stroke;
          this.ctx.fill();
          this.ctx.restore();
        }
      });
      
      
      
      fabric.LineArrow.fromObject = function(object, callback) {
        callback && callback(new fabric.LineArrow([object.x1, object.y1, object.x2, object.y2], object));
      };
      
      fabric.LineArrow.async = true;
      
      
      var Arrow = (function() {
        function Arrow(canvas) {
          this.canvas = canvas;
          this.className = 'Arrow';
          this.isDrawing = false;
          this.bindEvents();
        }
      
        Arrow.prototype.bindEvents = function() {
          var inst = this;
          inst.canvas.on('mouse:down', function(o) {
            inst.onMouseDown(o);
          });
          inst.canvas.on('mouse:move', function(o) {
            inst.onMouseMove(o);
          });
          inst.canvas.on('mouse:up', function(o) {
            inst.onMouseUp(o);
          });
          inst.canvas.on('object:moving', function(o) {
            inst.disable();
          })
        }
      
        Arrow.prototype.onMouseUp = function(o) {
          var inst = this;
          this.line.set({
            dirty: true,
            objectCaching: true
          });
          inst.canvas.renderAll();
          inst.disable();
        };
      
        Arrow.prototype.onMouseMove = function(o) {
          var inst = this;
          if (!inst.isEnable()) {
            return;
          }
      
          var pointer = inst.canvas.getPointer(o.e);
          var activeObj = inst.canvas.getActiveObject();
          activeObj.set({
            x2: pointer.x,
            y2: pointer.y
          });
          activeObj.setCoords();
          inst.canvas.renderAll();
        };
      
        Arrow.prototype.onMouseDown = function(o) {
          var inst = this;
          inst.enable();
          var pointer = inst.canvas.getPointer(o.e);
      
          var points = [pointer.x, pointer.y, pointer.x, pointer.y];
          this.line = new fabric.LineArrow(points, {
            strokeWidth: 5,
            fill: 'red',
            stroke: 'red',
            originX: 'center',
            originY: 'center',
            hasBorders: false,
            hasControls: false,
            objectCaching: false,
            perPixelTargetFind: true,
            heads: [1, 0]
          });
      
          inst.canvas.add(this.line).setActiveObject(this.line);
        };
      
        Arrow.prototype.isEnable = function() {
          return this.isDrawing;
        }
      
        Arrow.prototype.enable = function() {
          this.isDrawing = true;
        }
      
        Arrow.prototype.disable = function() {
          this.isDrawing = false;
        }
      
        return Arrow;
      }());
      
      var canvas = new fabric.Canvas('canvas', {
        selection: false
      });
      var arrow = new Arrow(canvas);
      <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.17/fabric.min.js"></script>
       Please draw arrow here
      
      <div id="canvasContainer">
        <canvas id="canvas" width="400" height="400" style="border: solid 1px"></canvas>
      </div>https://stackoverflow.com/questions/53114152/draw-two-head-arrows-in-fabric-js#

      附: Durga 的所有学分。我只是对他的代码做了一些小的修改。

      更新以使用动态笔画宽度

      要使用动态笔画宽度,drawArrow必须把strokeWidth变成一个三角形,所以drawArrow函数内部会有如下变化:

      this.ctx.moveTo(this.strokeWidth, 0);
      this.ctx.lineTo(-this.strokeWidth*2, this.strokeWidth*2);
      this.ctx.lineTo(-this.strokeWidth*2, -this.strokeWidth*2);
      

      最终代码在这里:

      // Extended fabric line class
      fabric.LineArrow = fabric.util.createClass(fabric.Line, {
      
        type: 'lineArrow',
      
        initialize: function(element, options) {
          options || (options = {});
          this.callSuper('initialize', element, options);
        },
      
        toObject: function() {
          return fabric.util.object.extend(this.callSuper('toObject'));
        },
      
        _render: function(ctx) {
          this.ctx = ctx;
          this.callSuper('_render', ctx);
          let p = this.calcLinePoints();
          let xDiff = this.x2 - this.x1;
          let yDiff = this.y2 - this.y1;
          let angle = Math.atan2(yDiff, xDiff);
          this.drawArrow(angle, p.x2, p.y2, this.heads[0]);
          ctx.save();
          xDiff = -this.x2 + this.x1;
          yDiff = -this.y2 + this.y1;
          angle = Math.atan2(yDiff, xDiff);
          this.drawArrow(angle, p.x1, p.y1,this.heads[1]);
        },
      
        drawArrow: function(angle, xPos, yPos, head) {
          this.ctx.save();
         
          if (head) {
              this.ctx.translate(xPos, yPos);
              this.ctx.rotate(angle);
            this.ctx.beginPath();
      
            this.ctx.moveTo(this.strokeWidth, 0);
            this.ctx.lineTo(-this.strokeWidth*2, this.strokeWidth*2);
            this.ctx.lineTo(-this.strokeWidth*2, -this.strokeWidth*2);
            this.ctx.closePath();
          }
          
          this.ctx.fillStyle = this.stroke;
          this.ctx.fill();
          this.ctx.restore();
        }
      });
      
      
      
      fabric.LineArrow.fromObject = function(object, callback) {
        callback && callback(new fabric.LineArrow([object.x1, object.y1, object.x2, object.y2], object));
      };
      
      fabric.LineArrow.async = true;
      
      
      var Arrow = (function() {
        function Arrow(canvas) {
          this.canvas = canvas;
          this.className = 'Arrow';
          this.isDrawing = false;
          this.bindEvents();
        }
      
        Arrow.prototype.bindEvents = function() {
          var inst = this;
          inst.canvas.on('mouse:down', function(o) {
            inst.onMouseDown(o);
          });
          inst.canvas.on('mouse:move', function(o) {
            inst.onMouseMove(o);
          });
          inst.canvas.on('mouse:up', function(o) {
            inst.onMouseUp(o);
          });
          inst.canvas.on('object:moving', function(o) {
            inst.disable();
          })
        }
      
        Arrow.prototype.onMouseUp = function(o) {
          var inst = this;
          this.line.set({
            dirty: true,
            objectCaching: true
          });
          inst.canvas.renderAll();
          inst.disable();
        };
      
        Arrow.prototype.onMouseMove = function(o) {
          var inst = this;
          if (!inst.isEnable()) {
            return;
          }
      
          var pointer = inst.canvas.getPointer(o.e);
          var activeObj = inst.canvas.getActiveObject();
          activeObj.set({
            x2: pointer.x,
            y2: pointer.y
          });
          activeObj.setCoords();
          inst.canvas.renderAll();
        };
      
        Arrow.prototype.onMouseDown = function(o) {
          var inst = this;
          inst.enable();
          var pointer = inst.canvas.getPointer(o.e);
      
          var points = [pointer.x, pointer.y, pointer.x, pointer.y];
          this.line = new fabric.LineArrow(points, {
            strokeWidth: 20,
            fill: 'red',
            stroke: 'red',
            originX: 'center',
            originY: 'center',
            hasBorders: false,
            hasControls: false,
            objectCaching: false,
            perPixelTargetFind: true,
            heads: [1, 0]
          });
      
          inst.canvas.add(this.line).setActiveObject(this.line);
        };
      
        Arrow.prototype.isEnable = function() {
          return this.isDrawing;
        }
      
        Arrow.prototype.enable = function() {
          this.isDrawing = true;
        }
      
        Arrow.prototype.disable = function() {
          this.isDrawing = false;
        }
      
        return Arrow;
      }());
      
      var canvas = new fabric.Canvas('canvas', {
        selection: false
      });
      var arrow = new Arrow(canvas);
      <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.17/fabric.min.js"></script>
       Please draw arrow here
      
      <div id="canvasContainer">
        <canvas id="canvas" width="800" height="800" style="border: solid 1px"></canvas>
      </div>

      【讨论】:

      • 你有使用 canvas.loadFromJSON() 的例子吗?
      • 很遗憾我没有
      • 嗨@Observer,感谢您的代码。我正在为我的项目使用您的代码来绘制箭头。但是当我使用strokeWidth: 20 时,三角形无法正确缩放。你能帮我重构这段代码以使用动态strokeWidth吗?
      猜你喜欢
      • 1970-01-01
      • 2013-11-30
      • 2020-12-09
      • 2018-11-18
      • 2012-05-09
      • 2014-01-25
      • 1970-01-01
      • 2020-09-07
      • 2014-11-01
      相关资源
      最近更新 更多