四边形绘制之后,显示每条线的中垂线;点击四边形内部,显示顶点并可移动四边形;拖动顶点,可拉伸四边形
<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;
};