在FabricJS中画出一条波浪线

Gue*_*dio 5 javascript canvas fabricjs

我正在使用FabricJS来创建用于绘制特定线条和形状的画布.其中一条线是一条波浪线,箭头与此类似:

在此输入图像描述

我已经用箭头端点成功创建了这个版本,但找不到任何如何创建波浪线的示例.用户可以根据需要绘制线条,因此线条中的"峰值"和"波谷"数量需要相应调整(如上图所示的短线可能有4个峰值但是长度为两倍的线条会有8个峰值,不仅仅是较短线的拉伸版本).

这是我用来绘制带箭头端点的直线的代码.请注意,该行的起点是在mousedown上绘制的,并且端点是在mouseup上绘制的.

import LineWithArrow from './LineWithArrow';

drawLineWithArrow = (item, points, color) => (
  new LineWithArrow(points, {
    customProps: item,
    strokeWidth: 2,
    stroke: color,
  })
)

selectLine = (item, points) => {
  switch (item.type) {
    case 'line_with_arrow':
      return this.drawLineWithArrow(item, points, colors.BLACK);

    case 'wavy_line_with_arrow':
      return this.drawWavyLineWithArrow(item, points);
    // no default
  }
  return null;
}

let line;
let isDown;

fabricCanvas.on('mouse:down', (options) => {
  isDown = true;
  const pointer = fabricCanvas.getPointer(options.e);
  const points = [pointer.x, pointer.y, pointer.x, pointer.y];
  line = this.selectLine(item, points);
  fabricCanvas
    .add(line)
    .setActiveObject(line)
    .renderAll();
});

fabricCanvas.on('mouse:move', (options) => {
  if (!isDown) return;
  const pointer = fabricCanvas.getPointer(options.e);
  line.set({ x2: pointer.x, y2: pointer.y });
  fabricCanvas.renderAll();
});

fabricCanvas.on('mouse:up', () => {
  isDown = false;
  line.setCoords();
  fabricCanvas.setActiveObject(line).renderAll();
});
Run Code Online (Sandbox Code Playgroud)

和LineWithArrow文件:

import { fabric } from 'fabric';

const LineWithArrow = fabric.util.createClass(fabric.Line, {
  type: 'line_with_arrow',

  initialize(element, options) {
    options || (options = {});
    this.callSuper('initialize', element, options);

    // Set default options
    this.set({
      hasBorders: false,
      hasControls: false,
    });
  },

  _render(ctx) {
    this.callSuper('_render', ctx);
    ctx.save();
    const xDiff = this.x2 - this.x1;
    const yDiff = this.y2 - this.y1;
    const angle = Math.atan2(yDiff, xDiff);
    ctx.translate((this.x2 - this.x1) / 2, (this.y2 - this.y1) / 2);
    ctx.rotate(angle);
    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)
    ctx.moveTo(5, 0);
    ctx.lineTo(-5, 5);
    ctx.lineTo(-5, -5);
    ctx.closePath();
    ctx.fillStyle = this.stroke;
    ctx.fill();
    ctx.restore();
  },

  toObject() {
    return fabric.util.object.extend(this.callSuper('toObject'), {
      customProps: this.customProps,
    });
  },
});

export default LineWithArrow;
Run Code Online (Sandbox Code Playgroud)

Pio*_*ski 7

结果

我不是真正的专家,但我试图自己实现波浪线。

结果是这样的:

来自 codepen.io 的箭头屏幕截图

编码

我用这个fabric.Group类来对构成我们波浪线的线条进行分组。

const WavyLineWithArrow = fabric.util.createClass(fabric.Group, {
    /* ... */
};
Run Code Online (Sandbox Code Playgroud)

每次更改后,这些行将被删除并添加到对象中:

this.forEachObject(function(o) {
    this.remove(o);
}, this);

for(var i=1;i<polyPoints.length;++i) {
    this.add(new fabric.Line([
      polyPoints[i-1].x,
      polyPoints[i-1].y,
      polyPoints[i].x,
      polyPoints[i].y
    ], options));
  }
Run Code Online (Sandbox Code Playgroud)

行尾的箭头也是一个对象:

  this.add(new fabric.Polyline([
    {x: len/2, y: -arrowSize/2},
    {x: len/2 + arrowSize/2, y: 0},
    {x: len/2, y: arrowSize/2},
    {x: len/2, y: -arrowSize/2}
  ], arrOptions));
Run Code Online (Sandbox Code Playgroud)

所有艰巨的任务都是计算函数值、缩放等,但这只是无聊的几何。

免责声明

我测试了我的波浪线实现,即使您支持其他功能(不是正弦),它似乎也能很好地工作。

我看到的只有一个问题,那就是在您的示例中,您从一个角到另一个角渲染了线条。

旋转波浪线没什么大不了的,但这就是我注意到的理想解决方案的所有差异。

花式类型的箭头

我做了以下漂亮的箭头:

箭头类型截图

// Default: sine
null

// Custom: tangens
[
    function(x) { return Math.max(-10, Math.min(Math.tan(x/2) / 3, 10)); },
    4 * Math.PI
]

// Custom: Triangle function
[
    function(x) {
      let g = x % 6;
      if(g<=3) return g*5;
      if(g>3) return (6-g)*5;
    },
    6
]

// Custom: Square function
[
    function(x) {
      let g = x % 6;
      if(g<=3) return 15;
      if(g>3) return -15;
    },
    6
]
Run Code Online (Sandbox Code Playgroud)

完整示例

下面我附上我剪下的工作波浪线。
您还可以在codepen.io上查看该片段

const WavyLineWithArrow = fabric.util.createClass(fabric.Group, {
    /* ... */
};
Run Code Online (Sandbox Code Playgroud)
this.forEachObject(function(o) {
    this.remove(o);
}, this);

for(var i=1;i<polyPoints.length;++i) {
    this.add(new fabric.Line([
      polyPoints[i-1].x,
      polyPoints[i-1].y,
      polyPoints[i].x,
      polyPoints[i].y
    ], options));
  }
Run Code Online (Sandbox Code Playgroud)


Dur*_*rga 6

由于我们从两个角绘制一条线,因此您可以在_render自定义类的方法中绘制波浪线。从最后我画一条线到 mid ,以显示它与箭头的连接。

演示

var line, isDown, evented;
var canvas = new fabric.Canvas('canvas', {
  perPixelTargetFind: true
});
draw();

function selection() {
  changeObjSelection(true);
  canvas.off('mouse:down');
  canvas.off('mouse:move');
  canvas.off('mouse:up');
  evented = false;
}

function draw() {
  changeObjSelection(false);
  if (!evented) {
    canvas.on('mouse:down', onMouseDown);
    canvas.on('mouse:move', onMouseMove);
    canvas.on('mouse:up', onMouseUp);
    evented = true;
  }
}

function clearCanvas() {
 canvas.clear();
}

function changeObjSelection(value) {
  canvas.selection = value;
  canvas.forEachObject(function(obj) {
    obj.selectable = value;
  })
  canvas.requestRenderAll();
}

function onMouseDown(options) {
  isDown = true;
  var pointer = canvas.getPointer(options.e);
  var points = [pointer.x, pointer.y, pointer.x, pointer.y];
  line = selectLine(points);
  canvas.add(line);
}

function onMouseMove(options) {
  if (!isDown) return;
  var pointer = canvas.getPointer(options.e);
  line.set({
    x2: pointer.x,
    y2: pointer.y
  });
  canvas.renderAll();

}

function onMouseUp(options) {
  isDown = false;
  line.setCoords();
  canvas.requestRenderAll();
}

function drawLineWithArrow(points, color) {
  return new fabric.LineWithArrow(points, {
    strokeWidth: 2,
    stroke: color,
    objectCaching: false,
    selectable: false
  })
}

function selectLine(points) {
  return drawLineWithArrow(points, 'black');
}

//Wavy line

(function(global) {
  'use strict';
  if (fabric.LineWithArrow) {
    fabric.warn('fabric.LineWithArrow is already defined.');
    return;
  }
  var clone = fabric.util.object.clone;
  fabric.LineWithArrow = fabric.util.createClass(fabric.Line, {
    type: 'lineWithArrow',

    initialize: function(element, options) {
      options || (options = {});
      this.callSuper('initialize', element, options);

      // Set default options
      this.set({
        hasBorders: false,
        hasControls: false,
      });
    },

    _render: function(ctx) {
      // this.callSuper('_render', ctx);
      ctx.save();
      const xDiff = this.x2 - this.x1;
      const yDiff = this.y2 - this.y1;
      const angle = Math.atan2(yDiff, xDiff);
      ctx.translate(xDiff / 2, yDiff / 2);
      ctx.rotate(angle);
      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)
      ctx.moveTo(5, 0);
      ctx.lineTo(-5, 5);
      ctx.lineTo(-5, -5);
      ctx.closePath();
      ctx.fillStyle = this.stroke;
      ctx.fill();
      ctx.restore();
      var p = this.calcLinePoints();
      var point = this.pointOnLine(this.point(p.x2, p.y2), this.point(p.x1, p.y1), 10)
      this.wavy(this.point(p.x1, p.y1), point, this.point(p.x2, p.y2), ctx);
      ctx.stroke();
    },

    point: function(x, y) {
      return {
        x: x,
        y: y
      };
    },

    wavy: function(from, to, endPoint, ctx) {
      var cx = 0,
        cy = 0,
        fx = from.x,
        fy = from.y,
        tx = to.x,
        ty = to.y,
        i = 0,
        step = 4,
        waveOffsetLength = 0,

        ang = Math.atan2(ty - fy, tx - fx),
        distance = Math.sqrt((fx - tx) * (fx - tx) + (fy - ty) * (fy - ty)),
        amplitude = -10,
        f = Math.PI * distance / 30;

      for (i; i <= distance; i += step) {
        waveOffsetLength = Math.sin((i / distance) * f) * amplitude;
        cx = from.x + Math.cos(ang) * i + Math.cos(ang - Math.PI / 2) * waveOffsetLength;
        cy = from.y + Math.sin(ang) * i + Math.sin(ang - Math.PI / 2) * waveOffsetLength;
        i > 0 ? ctx.lineTo(cx, cy) : ctx.moveTo(cx, cy);
      }
      ctx.lineTo(to.x, to.y);
      ctx.lineTo(endPoint.x, endPoint.y);
    },

    pointOnLine: function(point1, point2, dist) {
      var len = Math.sqrt(((point2.x - point1.x) * (point2.x - point1.x)) + ((point2.y - point1.y) * (point2.y - point1.y)));
      var t = (dist) / len;
      var x3 = ((1 - t) * point1.x) + (t * point2.x),
        y3 = ((1 - t) * point1.y) + (t * point2.y);
      return new fabric.Point(x3, y3);
    },

    toObject: function() {
      return fabric.util.object.extend(this.callSuper('toObject'), {
        customProps: this.customProps,
      });
    },
  });
  fabric.LineWithArrow.fromObject = function(object, callback) {
    function _callback(instance) {
      delete instance.points;
      callback && callback(instance);
    };
    var options = clone(object, true);
    options.points = [object.x1, object.y1, object.x2, object.y2];
    fabric.Object._fromObject('LineWithArrow', options, _callback, 'points');
  };
})(typeof exports !== 'undefined' ? exports : this);
Run Code Online (Sandbox Code Playgroud)
canvas {
  border: 2px dotted black;
}
Run Code Online (Sandbox Code Playgroud)
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<button type="button" onclick="selection()">selection</button>
<button type="button" onclick="draw()">draw</button>
<button type="button" onclick="clearCanvas()">clear</button>
<canvas id="canvas" width="400" height="400"></canvas>
Run Code Online (Sandbox Code Playgroud)

  • 我同意这是更好的答案,因为它只需要直接画布渲染而不是分组。好一个!:) (2认同)