Canvas自定义画笔,绘制四边形

四边形绘制之后,显示每条线的中垂线;点击四边形内部,显示顶点并可移动四边形;拖动顶点,可拉伸四边形

<template>
  <div>
   <!--  画布  -->
    <canvas id="myCanvas" width="960" height="540"> </canvas>
    <!--  重新绘制按钮  -->
    <Button @click="creatDraw">重新绘制</Button>
  </div>
</template>

<script>
import { isPointInPath, calcMidLine, judgeIntersect } from "./config";

export default {
  mounted() {
    this.canvas = document.getElementById("myCanvas");
    this.bindEvent();
  },
   data() {
    return {
      canvas: null, // canvas对象
      position: [], // 四边形四个点的坐标
      midPosition: [], // 中垂线坐标
      imgData: null, // 缓存四边形
      isDragging: false, // 能否拖拽
      selectIdx: -1, // 当前选中的顶点下标
      hasPoint: false, // 是否已经绘制了四个顶点
      isDrawing: false, // 是否正在绘制
      isMoving: false, // 是否可移动四边形
      point: [] // 点击四边形内部当下的点坐标
    };
  },
   methods: {
    // 初始化四边形
    initDraw(type = "", position = []) {
      if (type !== "draw") {
        position = this.position;
      }
      const ctx = this.canvas.getContext("2d");
      ctx.strokeStyle = "#FF0000";
      ctx.beginPath();
      ctx.lineWidth = 3;
      position.forEach((p, idx) => {
        if (idx === 0) {
          ctx.moveTo(...p);
        } else {
          ctx.lineTo(...p);
        }
      });
      ctx.closePath();
      ctx.stroke();
      if (this.position.length < 4) {
        return;
      }
      this.drawMidLine();
      this.imgData = ctx.getImageData(0, 0, 960, 540);
      type === "drag" && this.drawPoint();
    },
     // 手动绘制
    creatDraw() {
      this.clearCanvas("reset");
      this.isDrawing = true;
    },
    // 绘制中垂线
    drawMidLine() {
      this.position.forEach((p, idx) => {
        const mid =
          idx === 3
            ? calcMidLine(p, this.position[0], this.position)
            : calcMidLine(p, this.position[idx + 1], this.position);
        const ctx = this.canvas.getContext("2d");

        const headlen = 6; //自定义箭头线的长度
        const theta = 45; //自定义箭头线与直线的夹角,个人觉得45°刚刚好
        let arrowX, arrowY; //箭头线终点坐标
        // 计算各角度和对应的箭头终点坐标
        const angle =
          (Math.atan2(mid[0][1] - mid[1][1], mid[0][0] - mid[1][0]) * 180) /
          Math.PI;
        const angle1 = ((angle + theta) * Math.PI) / 180;
        const angle2 = ((angle - theta) * Math.PI) / 180;
        const topX = headlen * Math.cos(angle1);
        const topY = headlen * Math.sin(angle1);
        const botX = headlen * Math.cos(angle2);
        const botY = headlen * Math.sin(angle2);

        ctx.beginPath();
        ctx.lineWidth = 3;
        ctx.strokeStyle = "#ffa51c";
        //画直线
        ctx.moveTo(...mid[0]);
        ctx.lineTo(...mid[1]);

        arrowX = mid[1][0] + topX;
        arrowY = mid[1][1] + topY;
        //画上边箭头线
        ctx.moveTo(arrowX, arrowY);
        ctx.lineTo(mid[1][0], mid[1][1]);

        arrowX = mid[1][0] + botX;
        arrowY = mid[1][1] + botY;
        //画下边箭头线
        ctx.lineTo(arrowX, arrowY);
        ctx.closePath();
        ctx.fillStyle = "#ffa51c";
        ctx.fill();
        // 绘制文字
        const char = String.fromCharCode(65 + idx);
        ctx.font = "16px Georgia";
        const xm = (mid[0][0] + mid[1][0]) / 2;
        const ym = (mid[0][1] + mid[1][1]) / 2;
        ctx.fillText(char, xm, ym);
        ctx.stroke();
        this.midPosition[idx] = [...mid, [xm, ym], [char]];
      });
    },
    // 绘制四个顶点
    drawPoint() {
      this.hasPoint = true;
      const ctx = this.canvas.getContext("2d");
      this.position.forEach(p => {
        ctx.beginPath();
        ctx.lineWidth = 4;
        ctx.strokeStyle = "#FF0000";
        ctx.arc(...p, 4, 0, 2 * Math.PI);
        ctx.fillStyle = "#FF0000";
        ctx.fill();
        ctx.stroke();
      });
    },
    // 监听鼠标事件
    bindEvent() {
      this.handleMouseUp();
      this.handleMouseDown();
      this.handleMouseMove();
    },
    // 清空画布
    clearCanvas(type) {
      const ctx = this.canvas.getContext("2d");
      ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
      type === "cache" && ctx.putImageData(this.imgData, 0, 0);
      if (type === "reset") {
        this.position = [];
      }
    },
     // 鼠标按下事件
    handleMouseDown() {
      const that = this;
      this.canvas.onmousedown = function(e) {
        e = e || event;
        const x = e.offsetX;
        const y = e.offsetY;
        // 绘制图像
        if (that.isDrawing) {
          that.position.push([x, y]);
          that.clearCanvas();
          that.initDraw();
          if (that.position.length === 4) {
            that.isDrawing = false;
          }
        } else {
          if (that.position.length === 0) {
            return;
          }

          // 判断点击的位置是哪一个顶点
          that.position.forEach((p, idx) => {
            var line = Math.abs(
              Math.sqrt(Math.pow(p[0] - x, 2) + Math.pow(p[1] - y, 2))
            );
            if (line - 8 < 0) {
              that.isDragging = true;
              that.selectIdx = idx;
            }
          });

          // 判断是否点击了四边形内部
          if (isPointInPath(x, y, that.position)) {
            !that.hasPoint && that.drawPoint();
            if (!that.isDragging) {
              that.isMoving = true;
              that.point = [x, y];
            }
          } else if (that.hasPoint && !isPointInPath(x, y, that.position)) {
            that.clearCanvas("cache");
            that.hasPoint = false;
          }
        }
      };
    },
        // 拖拽重新绘制
    handleMouseMove() {
      const that = this;
      this.canvas.onmousemove = function(e) {
        e = e || event;
        const x = e.offsetX;
        const y = e.offsetY;
        // 手动绘制
        if (that.isDrawing && that.position.length > 0) {
          that.clearCanvas();
          that.initDraw("draw", that.position.concat([[x, y]]));
        }
        // 判断是否可以拖动顶点
        if (that.isDragging) {
          that.position[that.selectIdx] = [x, y];
          that.clearCanvas("drag");
          that.initDraw("drag");
        }
        // 移动整个四边形
        if (that.isMoving) {
          that.position.forEach(p => {
            p[0] += x - that.point[0];
            p[1] += y - that.point[1];
          });
          that.point = [x, y];
          that.clearCanvas();
          that.initDraw("drag");
        }
      };
    },
     // 停止拖拽
    handleMouseUp() {
      const that = this;
      this.canvas.onmouseup = function() {
        that.isDragging = false;
        that.isMoving = false;
      };
      this.canvas.onmouseout = function() {
        that.isDragging = false;
        that.isMoving = false;
      };
    },
    // 判断绘制的图形是否符合要求
    validateImg() {
      if (this.position.length === 0) {
        return 0;
      }
      let valid = 0;
      // 判断绘制的图形是否超出了边界
      this.position.forEach(p => {
        if (p[0] < 20 || p[0] > 940 || p[1] < 20 || p[1] > 520) {
          valid = 1;
        }
      });
      // 判断绘制的图形是否是四边形
      if (valid === 0) {
        for (let i = 0; i < 4; i++) {
          const arr = [0, 1, 2, 3];
          let num = 0;
          arr.forEach(a => {
            if (a !== i) {
              const xj = judgeIntersect(
                ...this.position[i],
                ...this.position[(i + 1) % 4],
                ...this.position[a],
                ...this.position[(a + 1) % 4]
              );
              if (xj) {
                num++;
              }
            }
          });
          if (num !== 2) {
            valid = 2;
            break;
          }
        }
      }
      return valid;
    }
   }
}
<script>

纯逻辑判断方法移出到js中

// 判断点是否在四边形内
export const isPointInPath = function(testx, testy, position) {
  let i = 0,
    j = 0,
    c = false;
  const nvert = 4;
  const vertx = position.map(p => p[0]);
  const verty = position.map(p => p[1]);

  for (i = 0, j = nvert - 1; i < nvert; j = i++) {
    if (
      verty[i] > testy != verty[j] > testy &&
      testx <
        ((vertx[j] - vertx[i]) * (testy - verty[i])) / (verty[j] - verty[i]) +
          vertx[i]
    ) {
      c = !c;
    }
  }
  return c;
};

// 中垂线长度
const LINE = 30;

// 计算中垂线的两个点坐标
export const calcMidLine = function(p1, p2, position) {
  let xm = (p1[0] + p2[0]) / 4;
  if(p2[0] - 2 * xm === 0) {
    p1 = [p1[0] -1,p1[1]-1];
    xm = (p1[0] + p2[0]) / 4;
  }

  const ym = (p1[1] + p2[1]) / 4;
  const e =
    (4 * xm * xm + 4 * ym * ym - 2 * p2[0] * xm - 2 * ym * p2[1]) /
    (p2[0] - 2 * xm);
  const d = (2 * ym - p2[1]) / (p2[0] - 2 * xm);

  const a = d * d + 1;
  const b = 2 * d * (e + 2 * xm) + 4 * ym;
  const c = LINE * LINE - 4 * ym * ym - (e + 2 * xm) * (e + 2 * xm);

  const y1 = (b + Math.sqrt(b * b + 4 * a * c)) / 2 / a;
  const x1 = y1 * d - e;
  const y2 = (b - Math.sqrt(b * b + 4 * a * c)) / 2 / a;
  const x2 = y2 * d - e;

  const x = xm + (xm + (xm + (xm + x1 / 2) / 2) / 2) / 2;
  const y = ym + (ym + (ym + (ym + y1 / 2) / 2) / 2) / 2;
  const inner = isPointInPath(x, y, position);
  const point = inner
    ? [
        [x1, y1],
        [x2, y2]
      ]
    : [
        [x2, y2],
        [x1, y1]
      ];
  return point;
};


//判断两条线段是否相交
export const judgeIntersect = function(x1, y1, x2, y2, x3, y3, x4, y4) {
  if (
    !(
      Math.min(x1, x2) <= Math.max(x3, x4) &&
      Math.min(y3, y4) <= Math.max(y1, y2) &&
      Math.min(x3, x4) <= Math.max(x1, x2) &&
      Math.min(y1, y2) <= Math.max(y3, y4)
    )
  ) {
    return false;
  }
  const u = (x3 - x1) * (y2 - y1) - (x2 - x1) * (y3 - y1);
  const v = (x4 - x1) * (y2 - y1) - (x2 - x1) * (y4 - y1);
  const w = (x1 - x3) * (y4 - y3) - (x4 - x3) * (y1 - y3);
  const z = (x2 - x3) * (y4 - y3) - (x4 - x3) * (y2 - y3);
  return u * v <= 0.00000001 && w * z <= 0.00000001;
};

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值