fabric 自定义图形:继承Line实现箭头直线

说明

fabric 只提供了几个基础图形,比如矩形、圆形、三角形等,如果要使用 箭头直线 ,就需要自定义图像实现

创建类

使用下面函数创建类

fabric.util.createClass(父类,{
    // 初始化方法,相当于构造方法
    initialize: function(points, options){

    },
    // 渲染图形,使用 canvas 原生方式
    _render: function(ctx) {

    }
})

参数解释:

  • 父类:表示继承该类
  • {}:是传入的属性、方法

分析 fabric.Line 源码

这里以 fabric.Line 类为例子,进行分析:

// 第一个参数:fabric.Object,表示继承该类
// 第二个参数:{},是传入的属性、方法
fabric.Line = fabric.util.createClass(fabric.Object, {
    /**
     * Type of an object
     * @type String
     * @default
     */
    type: 'line',
    /**
     * x value or first line edge
     * @type Number
     * @default
     */
    x1: 0,
    /**
     * y value or first line edge
     * @type Number
     * @default
     */
    y1: 0,
    /**
     * x value or second line edge
     * @type Number
     * @default
     */
    x2: 0,
    /**
     * y value or second line edge
     * @type Number
     * @default
     */
    y2: 0,
    /**
     * Constructor 初始化方法,相当于构造方法
     * @param {Array} [points] Array of points
     * @param {Object} [options] Options object
     * @return {fabric.Line} thisArg
     */
    initialize: function(points, options) {
      if (!points) {
        points = [0, 0, 0, 0];
      }
      this.callSuper('initialize', options); // 调用父类的 initialize 初始化方法
      this.set('x1', points[0]);
      this.set('y1', points[1]);
      this.set('x2', points[2]);
      this.set('y2', points[3]);
      this._setWidthHeight(options);
    },
    /** 
     * @private 计算宽度高度
     * @param {Object} [options] Options
     */
    _setWidthHeight: function(options) {
      options || (options = { });
      this.width = Math.abs(this.x2 - this.x1);
      this.height = Math.abs(this.y2 - this.y1);
      this.left = 'left' in options
        ? options.left
        : this._getLeftToOriginX();
      this.top = 'top' in options
        ? options.top
        : this._getTopToOriginY();
    },
    /**
     * @private 渲染图形,使用 canvas 原生方式
     * @param {CanvasRenderingContext2D} ctx Context to render on
     */
    _render: function(ctx) {
      ctx.beginPath();
      // 重新计算开始点、结束点的位置,因为是以中心点为参考点,即:中心点的左侧、上部都是负数
      var p = this.calcLinePoints();
      ctx.moveTo(p.x1, p.y1);
      ctx.lineTo(p.x2, p.y2);
      ctx.lineWidth = this.strokeWidth;
      // TODO: test this
      // make sure setting "fill" changes color of a line
      // (by copying fillStyle to strokeStyle, since line is stroked, not filled)
      var origStrokeStyle = ctx.strokeStyle;
      ctx.strokeStyle = this.stroke || ctx.fillStyle;
      this.stroke && this._renderStroke(ctx);
      ctx.strokeStyle = origStrokeStyle;
    },
})

例子

<canvas id="canvas"></canvas>

定义图形类

<script>

  // Initiate a canvas instance
  var canvas = new fabric.Canvas("canvas");
  canvas.setWidth(document.body.scrollWidth);
  canvas.setHeight(250);

  const ArrowLine = fabric.util.createClass(fabric.Line,{

      type:"arrowLine", // 指定类型
      /**
       * Constructor
       * @param {Array} [points] 开始点x,y坐标
       * @param {number} [length] 箭头直线的长度
       * @param {Object} [options] Options object
       * @return {fabric.Line} thisArg
       */
      initialize: function(points,length, options) {
        if (!points) {
          points = [0, 0];
        }
        // 计算终点位置
        const end = [  points[0] + length , points[1]]
        // 添加到 points中,传给 父类构造方法,让其计算
        points.push( end[0] )
        points.push( end[1] )

        this.callSuper('initialize', points,options)

      },

      /**
       * @private 绘制图形,调用 canvas 原生方法
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _render: function(ctx) {
        ctx.beginPath();
        // 计算点的位置,这里的坐标,是以中心点为参考点
        var p = this.calcLinePoints();
        // 向右箭头的水平宽度
        const widthArrow = this._getArrowWidth()
        ctx.moveTo(p.x1, p.y1);
        ctx.lineTo( this._getLineEndX(p), p.y2);

        this._drawRightArrow(ctx,p)

        ctx.lineWidth = this.strokeWidth;

        // TODO: test this
        // make sure setting "fill" changes color of a line
        // (by copying fillStyle to strokeStyle, since line is stroked, not filled)
        var origStrokeStyle = ctx.strokeStyle;
        // ctx.strokeStyle = this.stroke || ctx.fillStyle;
        // ctx.fillStyle = this.fill 
        this.stroke && this._renderStroke(ctx);
        // 填充箭头颜色
        this.fill && this._renderFill(ctx);
        ctx.strokeStyle = origStrokeStyle;

      },
      /**
       * 获取向右箭头水平宽度
       */
      _getArrowWidth(){
          // 向右箭头的水平宽度
          return this.strokeWidth * 2 + 8
      },
      /**
       * 直线结束 x 的坐标
       * @param {Object} p
       */
      _getLineEndX(p){
        const widthArrow = this._getArrowWidth()
        return p.x2 - widthArrow // 直线减去箭头的水平宽度
      },
      /**
       * 绘制向右箭头箭头
       * @param {Object} ctx
       * @param {Object} p
       */
      _drawRightArrow(ctx,p){
          // 向右箭头的垂直高度
          const heightArrow = this.strokeWidth * 2 + 5

          // 向右箭头的水平宽度
          const widthArrow = this._getArrowWidth()
          // console.log("widthArrow:",widthArrow)

          // 直线的长度
          const lengthLine = this.width - widthArrow
          // 直线结束点位置
          const end = [ this._getLineEndX(p),0]
          // 向右箭头,右点位置
          const rightArrow = [end[0] + widthArrow , 0]
          // 上点位置
          const topArrow = [end[0], -heightArrow/2]
          // 下点位置
          const bottomArrow = [end[0],heightArrow/2]


          ctx.moveTo(rightArrow[0],rightArrow[1])
          ctx.lineTo(topArrow[0],topArrow[1])
          ctx.lineTo(bottomArrow[0],bottomArrow[1])
          ctx.closePath()


      },
  })

  // 只能左右拉伸
  function setControlVisible(element){
    element['setControlVisible']('mt',false)
    element['setControlVisible']('mb',false)
    element['setControlVisible']('tl',false)
    element['setControlVisible']('bl',false)
    element['setControlVisible']('tr',false)
    element['setControlVisible']('br',false)
}
    const arrowLine = new ArrowLine([10,10] , 200 ,{
        left:100,
        top:100,
        strokeWidth: 3,
        stroke: "green",
        fill: "green",
    })
    setControlVisible(arrowLine)
    canvas.add(arrowLine)
    console.log(arrowLine.width,arrowLine.height)

  arrowLine.width = 300 // 动态改变宽度
  arrowLine.setCoords()
</script>

参考:
https://cloud.tencent.com/developer/article/2119137
https://www.cnblogs.com/k21vin/articles/17791973.html