1、有效区域最多5个
2、有效区域面积不小于画布的1/100
3、不可拖拽超出画布
4、绘制的是四边形
<template>
<div class="ai-draw-area">
<!-- 背景图 -->
<img :src="captureImg" class="img"/>
<!-- 画布 -->
<canvas id="myCanvas" class="canvas" width="640" height="360"></canvas>
</div>
</template>
<script>
import { hasIntersect, isPointInPath, calcArea } from "./config";
export default {
name: "ai-draw-area",
data() {
return {
canvas: null, // canvas对象
isDrawing: false, // 是否正在绘制
pointArr: [], // 四边形坐标数组
position: [], // 正在绘制的四边形四个点的坐标
focusIdx: -1, // 选中的四边形的下标
isDragging: false, // 能否拖拽
pointIdx: -1, // 当前选中的顶点下标
isMoving: false, // 是否可移动四边形
point: [], // 点击四边形内部当下的点坐标
isSaving: false // 保存中
}
},
props: {
// 抓拍的图片
captureImg: {
type: String,
default: ''
}
},
mounted() {
this.canvas = document.getElementById("myCanvas");
this.bindEvent();
},
methods: {
// 监听鼠标事件
bindEvent () {
this.handleMouseUp();
this.handleMouseDown();
this.handleMouseMove();
},
// 绘制四边形
drawQuadrangle(position, color = '#ffffff') {
const ctx = this.canvas.getContext("2d");
ctx.strokeStyle = color;
ctx.beginPath();
ctx.lineWidth = color === '#ffffff' ? 1 : 3;
position.forEach((p, idx) => {
if (idx === 0) {
ctx.moveTo(...p);
} else {
ctx.lineTo(...p);
}
});
ctx.closePath();
// 填充
if (position.length === 4) {
ctx.fillStyle = color === '#ffffff' ? 'rgba(255,255,255,0.3)' : 'rgba(255,153,0,0.3)';
ctx.fill();
}
ctx.stroke();
},
// 重新绘制所有之前绘制过的四边形
reloadQuadrangle() {
for(let i = 0; i< this.pointArr.length; i++) {
const color = this.focusIdx === i ? '#ff9900' : '#ffffff';
this.drawQuadrangle(this.pointArr[i], color);
}
},
// 绘制四个顶点
drawPoint () {
if (this.position.length !== 4) {
return;
}
const ctx = this.canvas.getContext("2d");
this.position.forEach(p => {
ctx.beginPath();
ctx.lineWidth = 4;
ctx.strokeStyle = "#ff9900";
ctx.arc(...p, 4, 0, 2 * Math.PI);
ctx.fillStyle = "#ff9900";
ctx.fill();
ctx.stroke();
});
},
// 清空画布
clearCanvas (type) {
const ctx = this.canvas.getContext("2d");
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// 更新画布,所有四边形重新绘制,同时绘制选中四边形的四个顶点
if (type === "reload") {
this.reloadQuadrangle();
this.focusIdx !== -1 && this.drawPoint();
}
},
// 校验绘制的四边形是否合格
verifyImg() {
// 不能交叉
const has = hasIntersect(this.position);
let msg = has ? 'ai_area_err' : '';
if (!msg) {
// 面积不能小于2400px²
const area = calcArea(this.position);
msg = area < 2400 ? 'ai_area_sum_err' : '';
}
return msg;
},
// 鼠标按下事件
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('reload');
that.drawQuadrangle(that.position, '#ff9900');
// 绘制完成变为选中状态
if (that.position.length === 4) {
const msg = that.verifyImg();
if (msg) {
that.position = [];
that.clearCanvas('reload');
that.$Message.error({content: that.$t("component." + msg), duration: 3});
return;
}
that.isDrawing = false;
that.pointArr.push(that.position);
that.focusIdx = that.pointArr.length - 1;
that.drawPoint();
}
} else {
if (that.focusIdx !== -1) {
// 判断点击的位置是不是顶点,顶点可拖拽
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) {
that.isDragging = true;
that.pointIdx = idx;
}
});
}
if (that.isDragging) {
return;
}
const selectIdx = that.judgeSelectElement(x, y);
// 现在选中的四边形和之前选中的不一样
if (that.focusIdx !== selectIdx) {
that.focusIdx = selectIdx;
that.clearCanvas('reload');
// 现在未选中任何四边形
if (selectIdx === -1) {
that.position = [];
} else {
// 绘制选中四边形的四个顶点
that.position = that.pointArr[selectIdx];
that.drawPoint();
that.isDragging = false;
that.isMoving = true;
that.point = [x, y];
}
} else {
// 现成选中的四边形和之前选中的一样,点击的是四边形内部则可平移
that.isMoving = true;
that.point = [x, y];
}
}
};
},
// 拖拽重新绘制
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('reload');
that.drawQuadrangle(that.position.concat([[x, y]]), '#ff9900');
}
// 判断是否可以拖动顶点
if (that.isDragging) {
that.position[that.pointIdx] = [x, y];
if (that.position.length === 4) {
const msg = that.verifyImg();
if (msg) {
that.isDragging = false;
that.position = [];
that.pointArr.splice(that.focusIdx, 1);
that.focusIdx = -1;
that.$Message.error({content: that.$t("component." + msg), duration: 3});
}
}
that.clearCanvas("reload");
}
// 移动整个四边形
if (that.isMoving) {
const poins = JSON.parse(JSON.stringify(that.position));
// 是否超出了画布
let isOuter = false;
poins.forEach(p => {
p[0] += x - that.point[0];
p[1] += y - that.point[1];
if (p[0] < 0 || p[0] > 640 || p[1] < 0 || p[1] > 360) {
isOuter = true;
}
});
if (!isOuter) {
that.position = poins;
that.pointArr[that.focusIdx] = that.position;
that.point = [x, y];
that.clearCanvas('reload');
}
}
};
},
// 停止拖拽
handleMouseUp () {
const that = this;
this.canvas.onmouseup = function () {
that.isDragging = false;
that.isMoving = false;
};
this.canvas.onmouseout = function () {
that.isDragging = false;
that.isMoving = false;
};
},
// 判断当前触动是哪一个四边形
judgeSelectElement(x, y) {
if (this.pointArr.length === 0) {
return -1;
}
// 重叠时,移动后绘制的四边形,所以从后往前遍历
let selectIdx = -1;
for(let i = this.pointArr.length - 1;i >= 0; i--) {
const inPath = isPointInPath(x, y, this.pointArr[i]);
if (inPath) {
selectIdx = i;
break;
}
}
return selectIdx;
},
// 添加
startDraw() {
if (this.pointArr.length === 5) {
this.$Message.error(this.$t("component.ai_area_num_err"));
return;
}
this.isDrawing = true;
this.focusIdx = -1;
this.position = [];
},
// 删除 选中的区域
deleteOne() {
if (this.focusIdx !== -1) {
this.position = [];
this.pointArr.splice(this.focusIdx, 1);
this.focusIdx = -1;
this.clearCanvas('reload');
}
},
// 全部删除
deleteAll() {
this.clearCanvas();
this.focusIdx = -1;
this.pointArr = [];
this.position = [];
}
}
}
</script>
<style scoped lang="less">
.ai-draw-area {
width: 640px;
height: 360px;
position: absolute;
z-index: 100;
background: #fff;
.img {
width: 640px;
height: 360px;
}
.canvas {
position: absolute;
top: 0;
left: 0;
}
}
</style>
校验四边形的各种方法
//判断两条线段是否相交
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;
};
// 判断四边形是否有两条边是相交的,即是否是合格的四边形
export const hasIntersect = function (points) {
let valid = false;
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(
...points[i],
...points[(i + 1) % 4],
...points[a],
...points[(a + 1) % 4]
);
if (xj) {
num++;
}
}
});
if (num !== 2) {
valid = true;
break;
}
}
return valid;
};
// 判断点是否在四边形内
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;
};
// 计算四边形的面积
export const calcArea = function (poins) {
const lens = [];
let sum = 0;
for(let i = 0; i < 4; i++) {
const j = i+1 === 4 ? 0 : i+1;
const len = Math.abs(Math.sqrt(Math.pow((poins[i][0] - poins[j][0]), 2) + Math.pow((poins[i][1] - poins[j][1]), 2)));
lens.push(len);
sum+=len;
}
const z = sum / 2;
const area = Math.sqrt((z-lens[0])*(z-lens[1])*(z-lens[2])*(z-lens[3]));
return area;
};