如何用fabricjs 画一个带箭头的折线,并且回显到画布

421 阅读1分钟

功能介绍

该功能是在fabricjs画布上通过鼠标按下,拖动,点击选点,双击完成绘制来画一个带有折线箭头的功能,做这个功能主要是因为目前该类型的多数场景都只是支持直线箭头,所以我觉得有必要记录一下。

遇到的问题

  1. 如何画箭头折线
  2. 如何画折线末尾的三角形
  3. 如何回显
  4. ...等等

代码实现

<template>
  <div>
    <p style="color: red">
      鼠标点击画布 移动鼠标 ,然后点击 再移动 再双击即可绘制折线
    </p>
    <div>
      <button @click="save">保存数据</button>
    </div>
    <div style="display: flex; width: 100%; height: 100%">
      <canvas id="canvas" style="border: 1px solid #ccc"></canvas>
      <textarea v-if="content" v-model="content" cols="30" rows="10"></textarea>
    </div>
  </div>
</template>

<script setup>
import { fabric } from "fabric";
import { onMounted, ref } from "vue";
// 将原点坐标放到对象的中心
fabric.Object.prototype.originX = "center";
fabric.Object.prototype.originY = "center";

// 更改后 获取新的坐标点
function getPoints(obj) {
  var matrix = [];
  matrix = obj.calcTransformMatrix();
  var translatedPoints = obj.get("points").map(function (p) {
    return {
      x: p.x - obj.pathOffset.x,
      y: p.y - obj.pathOffset.y,
    };
  });
  for (var i = 0; i < translatedPoints.length; i++) {
    translatedPoints[i].x =
      matrix[0] * translatedPoints[i].x +
      matrix[2] * translatedPoints[i].y +
      matrix[4];
    translatedPoints[i].y =
      matrix[1] * translatedPoints[i].x +
      matrix[3] * translatedPoints[i].y +
      matrix[5];
  }
  return translatedPoints;
}

// 获取角度
function getAngle(points) {
  let p1 = points[points.length - 1];
  let p2 = points[points.length - 2];
  const xDiff = p1.x - p2.x;
  const yDiff = p1.y - p2.y;
  // 初始角度  我搞了个瘦长的箭头 先默认让箭头平躺下来
  let angle = 90;
  angle += (Math.atan2(yDiff, xDiff) * 180) / Math.PI;
  console.log(angle);
  return angle;
}

const content = ref(null);

let canvas;
onMounted(() => {
  // 回显的数据
  const data = [{"left":197.71481145862157,"top":246.5,"angle":0,"scaleX":1,"scaleY":1,"fill":"rgb(0,0,0)","points":[{"x":109.5,"y":146},{"x":93.5,"y":347},{"x":285.5,"y":331},{"x":290.5,"y":243}],"angle2":3.2519456003638822},{"left":508.5735325128169,"top":276,"angle":0,"scaleX":1,"scaleY":1,"fill":"rgb(0,0,0)","points":[{"x":449.5,"y":151},{"x":450.5,"y":333},{"x":404.5,"y":401},{"x":597.5,"y":326},{"x":601.5,"y":238}],"angle2":2.6025622024998114},{"left":432.6213452675692,"top":524.0556139716009,"angle":0,"scaleX":1,"scaleY":1,"fill":"rgb(0,0,0)","points":[{"x":250.5,"y":501},{"x":421.5,"y":562},{"x":511.5,"y":484},{"x":588.5,"y":541}],"angle2":126.5110561194953}]

  canvas = new fabric.Canvas("canvas", {
    selection: false,
    width: 1000,
    height: 1000,
  });

  data.map((v) => {
    let points = v["points"];
    let angle = v["angle2"];
    let p1 = points[points.length - 1];
    console.log(angle);
    let polyline = new fabric.Polyline(points, {
      stroke: "#000",
      fill: "transparent",
    });
    // 注意 三角形的大小和初始旋转角度很重要
    var triangle = new fabric.Triangle({
      width: 20,
      height: 50,
      fill: "#000",
      left: p1.x,
      top: p1.y,
      angle,
    });
    let group = new fabric.Group([polyline, triangle]);
    canvas.add(group);
    group.setControlsVisibility({
      mtr: true,
      bl: true,
      br: true,
      mb: false,
      ml: false,
      mr: false,
      mt: false,
      tl: true,
      tr: true,
    });
  });

  function drawPolyLineArrow(e, canvas) {
    const currentPoint = e.absolutePointer;
    let currentPolyline = new fabric.Polyline(
      [
        { x: currentPoint.x, y: currentPoint.y },
        { x: currentPoint.x, y: currentPoint.y },
      ],
      {
        fill: "transparent",
        stroke: "rgba(0, 0, 0, 0.2)",
        objectCaching: false,
      }
    );
    canvas.currentShape = currentPolyline;
    canvas.add(currentPolyline);
  }

  let drawing = false;
  canvas.on("mouse:down", function (opt) {
    if (canvas.getActiveObject()) {
      return;
    }
    const { x, y } = opt.pointer;
    canvas.startX = x;
    canvas.startY = y;
    if (!drawing) {
      // 如果是第一次
      drawing = true;
      drawPolyLineArrow(opt, canvas);
    } else {
      // 当没有松开鼠标 并且移动点击的时候
      const currentPoint = opt.absolutePointer;
      let points = canvas.currentShape.points;
      points.push({
        x: currentPoint.x,
        y: currentPoint.y,
      });
      canvas.requestRenderAll();
    }
  });

  // 移动鼠标事件
  canvas.on("mouse:move", function (opt) {
    if (drawing) {
      if (canvas.currentShape) {
        const currentPoint = opt.absolutePointer;
        let points = canvas.currentShape.points;
        points[points.length - 1].x = currentPoint.x;
        points[points.length - 1].y = currentPoint.y;
        canvas.requestRenderAll();
      }
    }
  });

  // 松开鼠标事件
  canvas.on("mouse:up", function (opt) {
    if (canvas.currentShape) {
      return;
    }
    drawing = false;
    canvas.currentShape = null;
  });

  canvas.on("mouse:dblclick", function (opt) {
    if (canvas.currentShape) {
      const currentPoint = opt.absolutePointer;
      let points = canvas.currentShape.points;
      console.log(points);
      points[points.length - 1].x = currentPoint.x;
      points[points.length - 1].y = currentPoint.y;
      points.pop();
      points.pop();
      canvas.remove(canvas.currentShape);
      console.log(points);
      if (points.length > 1) {
        let p1 = points[points.length - 1];
        let angle = getAngle(points);
        let polyline = new fabric.Polyline(points, {
          stroke: "#000",
          fill: "transparent",
        });
        // 注意 三角形的大小和初始旋转角度很重要
        var triangle = new fabric.Triangle({
          width: 20,
          height: 50,
          fill: "#000",
          left: p1.x,
          top: p1.y,
          angle,
        });
        let group = new fabric.Group([polyline, triangle]);
        // 由于目前拉伸四个边的控制 会导致一些问题( 主要是三角形的角度坐标会出毛病) ,所以这里我选择不让拉伸变形 只能等比例拉伸
        group.setControlsVisibility({
          mtr: true,
          bl: true,
          br: true,
          mb: false,
          ml: false,
          mr: false,
          mt: false,
          tl: true,
          tr: true,
        });
        canvas.add(group);
      }
      canvas.requestRenderAll();
      canvas.currentShape = null;
      drawing = false;
    }
  });
});

// 根据需要保存一些key到后台,当然你也可以自定义一些你的数据保存
const saveTypesMap = {
  left: true,
  top: true,
  angle: true,
  scaleX: true,
  scaleY: true,
  fill: true,
};

function save() {
  let data = [];
  canvas.forEachObject((obj) => {
    let json = {};
    for (const key in saveTypesMap) {
      json[key] = obj[key];
    }
    let points = getPoints(obj.item(0));
    let angle2 = obj.item(1).angle;
    json["points"] = points;
    json["angle2"] = angle2;
    data.push(json);
  });
  content.value = JSON.stringify(data);
}
</script>