5、Fabric.js 如何实现带箭头的折线

当我们在特定的场景可能需要进行元素之间的连线,这一篇主要讲如何实现带箭头的折线。
在这里插入图片描述 在这里插入图片描述

折线部分 new fabric.Path

官网地址
在这里插入图片描述
其实折线也算是不规则图形,只不过没有背景颜色fill
先不加两端元素(圆圈和箭头)。
示例(有背景色):

/**
 * 绘制路径 折线
 * @param coords 路径的起点和转折点
 */
function addPathLine(coords = 'M 100 100 L 300 100 L 300 300') {
  let line = new fabric.Path(coords, {
    fill: 'red',
    stroke: 'rgba(0,195,244)',
    strokeWidth: 4,
    objectCaching: false,
    // 禁止选中
    selectable: false,
    hasBorders: false, //当设置为 `false` 时,对象的控制边界不会被渲染
    hasControls: false, // 当设置为 `false` 时,对象的控件不显示并且不能用于操作对象
    hoverCursor: 'default',
    name: `${ElCircleEnum.PATH_LINE}`,
  });
  canvasContainer.add(line);
}

在这里插入图片描述
如果 coords = ‘M 100 100 L 300 100 L 300 300 Z’,在最后面加一个Z,则会使不规则图形闭合,如下图所示:
在这里插入图片描述
接下来为了数据查看和存储方便我们用数组来存放coords。

/**
 * 获取路径字符串
 * @param coords
 */
function getPathByCoords(coords) {
  let str = '';
  (coords || []).forEach((cItem, cIndex) => {
    if (cIndex == 0) {
      str = cItem.join(' ');
    } else {
      str = str + ' ' + cItem.join(' ');
    }
  });
  return str;
}

/**
 * 绘制路径 折线
 * @param coords 路径的起点和转折点
 */
function addPathLine(
  coords = [
    ['M', 100, 100],
    ['L', 300, 100],
    ['L', 300, 300],
  ]
) {
  let line = new fabric.Path(getPathByCoords(coords), {
    fill: '',
    stroke: 'rgba(0,195,244)',
    strokeWidth: 4,
    objectCaching: false,
    // 禁止选中
    selectable: false,
    hasBorders: false, //当设置为 `false` 时,对象的控制边界不会被渲染
    hasControls: false, // 当设置为 `false` 时,对象的控件不显示并且不能用于操作对象
    hoverCursor: 'default',
    name: `${ElCircleEnum.PATH_LINE}`,
  });
  canvasContainer.add(line);
}

起点圆圈部分 new fabric.Circle

// 拖拽点大小
const DROG_CIRCLE = 8;

/**
 * 绘制
 * @param left
 * @param top
 * @param fill
 */
function makeCurveCircle(left, top, fill = '#13416e') {
  return new fabric.Circle({
    left: left,
    top: top,
    strokeWidth: 2,
    radius: DROG_CIRCLE - 1,
    // fill: '#fff',
    // stroke: '#666',
    fill,
    stroke: '#96cbf5',
    // hasBorders: true, // 它指定是否使边框可见
    hasControls: false, // 它指定是否禁用控件
    opacity: 0.8,
    centeredRotation: true,
    originX: 'center',
    originY: 'center',
  });
}

/**
 * 绘制路径 折线
 * @param coords 路径的起点和转折点
 */
function addPathLine(
  coords = [
    ['M', 100, 100],
    ['L', 300, 100],
    ['L', 300, 300],
  ]
) {
  ...
  // 起点的拖拽图标
  const cStart = makeCurveCircle(coords[0][1], coords[0][2]);
  cStart.name = `${ElCircleEnum.START_CIRCLE}`;
  canvasContainer.add(cStart);
}

在这里插入图片描述

终点三角形部分 new fabric.Triangle

/**
 * 绘制箭头
 */
function drawEndArrow(left, top) {
  return new fabric.Triangle({
    width: DROG_CIRCLE * 2,
    height: DROG_CIRCLE * 2,
    left: left,
    top: top,
    fill: 'rgba(0,195,244)',
    opacity: 1,
    centeredRotation: true,
    originX: 'center',
    originY: 'center',
    // 禁止选中
    selectable: true,
    hasControls: false, // 当设置为 `false` 时,对象的控件不显示并且不能用于操作对象
  });
}

/**
 * 绘制路径 折线
 * @param coords 路径的起点和转折点
 */
function addPathLine(
  coords = [
    ['M', 100, 100],
    ['L', 300, 100],
    ['L', 300, 300],
  ]
) {
  ...
  // 终点的拖拽图标
  const cEnd = drawEndArrow(
    coords[coords.length - 1][1],
    coords[coords.length - 1][2]
  );

  cEnd.name = `${ElCircleEnum.END_CIRCLE}`;
  canvasContainer.add(cEnd);
}

在这里插入图片描述
虽然三角形绘制成功,但是作为箭头肯定是指向出口的嘛!这显然不对,所以我们还需要控制一下箭头的旋转方向。我们可以根据线段路径的最后两个点坐标来控制旋转方向,因为两点成线,并且折线只会有两种方向:

  • 垂直方向(x相同):如果最后一个点的y大于倒数第二个点的y坐标,则旋转180°(C),反之旋转0°(D)
  • 水平方向(y相同):如果最后一个点的x大于倒数第二个点的x坐标,则旋转90°(A),反之旋转270°(B)
    在这里插入图片描述
    示例:
/**
 * 根据绘制的路径判断终点箭头的旋转方向
 * @param coords
 */
function getTriangleAngleByCoords(coords) {
  if (coords.length < 2) return 0;
  // 最后一个点
  const x1 = coords[coords.length - 1][1];
  const y1 = coords[coords.length - 1][2];
  // 倒数第二个点
  const x2 = coords[coords.length - 2][1];
  const y2 = coords[coords.length - 2][2];
  let angle = 0;
  if (x1 < x2 && checkSame(y1, y2)) {
    angle = 270;
  }
  if (x1 > x2 && checkSame(y1, y2)) {
    angle = 90;
  }
  if (y1 > y2 && checkSame(x1, x2)) {
    angle = 180;
  }
  if (y1 < y2 && checkSame(x1, x2)) {
    angle = 0;
  }
  return angle;
}

function checkSame(num1, num2) {
  return num1.toFixed(6) == num2.toFixed(6);
}

/**
 * 绘制箭头
 */
function drawEndArrow(left, top, coords) {
  const angle = getTriangleAngleByCoords(coords);
  return new fabric.Triangle({
    width: DROG_CIRCLE * 2,
    height: DROG_CIRCLE * 2,
    left: left,
    top: top,
    fill: 'rgba(0,195,244)',
    angle,
    opacity: 1,
    centeredRotation: true,
    originX: 'center',
    originY: 'center',
    // 禁止选中
    selectable: true,
    hasControls: false, // 当设置为 `false` 时,对象的控件不显示并且不能用于操作对象
  });
}

/**
 * 绘制路径 折线
 * @param coords 路径的起点和转折点
 */
function addPathLine(
  coords = [
    ['M', 100, 100],
    ['L', 300, 100],
    ['L', 300, 300],
  ]
) {
  ...
  // 终点的拖拽图标
  const cEnd = drawEndArrow(
    coords[coords.length - 1][1],
    coords[coords.length - 1][2],
    coords
  );

  cEnd.name = `${ElCircleEnum.END_CIRCLE}`;
  canvasContainer.add(cEnd);
}

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值