1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
|
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>使用Canvas绘制不规则图形</title>
<style type="text/css">
#canvas-container {
border: 1px solid red;
}
</style>
<script text="application/javascript">
let canvas;
let ctx;
let tempCanvas;
let tempCtx;
// 初始化画布
function initCanvas(canvasId, tempCanvasId) {
canvas = document.getElementById(canvasId);
if (canvas.getContext) {
ctx = canvas.getContext("2d");
canvas.addEventListener('mousedown', mousedown);
}
tempCanvas = document.getElementById(tempCanvasId);
if (tempCanvas.getContext) {
tempCtx = tempCanvas.getContext("2d");
tempCanvas.addEventListener('mousedown', mousedown);
}
}
const CANVAS_WIDTH = 300;
const CANVAS_HEIGHT = 150;
const MAX_POINT_NUM = 8;
// 记录所有点位
let pointList = [];
// 记录当前闭合状态
let closeStatus = false;
// 鼠标按下事件:开始绘制图形
const mousedown = (e) => {
// 记录当前点击点位,并将其转换为画布尺寸
let pointDown = {
x: e.offsetX / canvas.offsetWidth * CANVAS_WIDTH,
y: e.offsetY / canvas.offsetHeight * CANVAS_HEIGHT
};
if (pointList.length === 0) {
// 是第一个点:初始化绘制事件
ctx.beginPath();
ctx.moveTo(pointDown.x, pointDown.y);
} else if (closeStatus) {
// 图形已闭合:不再绘制
return;
} else {
// 其他点位,和上一个点进行连线
ctx.lineTo(pointDown.x, pointDown.y);
ctx.stroke();
}
// 记录所有点位
pointList.push({
...pointDown
});
// 最大点数,闭合图形
if (pointList.length >= MAX_POINT_NUM) {
// 这里采用限制点数的方式进行图形闭合,也可以采用其他方法闭合,如点击起始点附件 x 个元素时进行闭合
closeFigure();
return;
}
// 鼠标跟随移动
document.onmousemove = (event) => {
tempCtx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
tempCtx.beginPath();
tempCtx.moveTo(pointDown.x, pointDown.y);
tempCtx.lineTo(event.offsetX / tempCanvas.offsetWidth * CANVAS_WIDTH, event.offsetY / tempCanvas.offsetHeight * CANVAS_HEIGHT);
tempCtx.stroke();
}
}
// 重置画布
function clearRect() {
document.onmousemove = document.onmouseup = null;
ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
tempCtx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
pointList = [];
closeStatus = false;
}
function closeFigure() {
if (!closeStatus && checkPointCross()) {
// 存在点位交叉:不符合绘制要求,重绘
// ToDo: 错误提示
clearRect();
};
if (pointList.length >= MAX_POINT_NUM && !closeStatus) {
// 符合要求:结束绘制
document.onmousemove = document.onmouseup = null;
tempCtx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
ctx.lineTo(pointList[0].x, pointList[0].y);
ctx.closePath();
ctx.stroke();
closeStatus = true;
}
}
// 辅助函数:获取以原点为起点的向量坐标表达式
function getPointVector(startPoint, endPoint) {
return {
x: endPoint.x - startPoint.x,
y: endPoint.y - startPoint.y,
};
}
// 辅助函数:叉乘
const crossLine = (point1, point2) => {
return point1.x * point2.y - point2.x * point1.y;
}
/**
* 辅助函数:判断两条线段是否交叉
* 以line1_start_point为点A,line1_end_point为点B,line2_start_point为点C,line2_end_point为点D
* 做两线AB、CD
* 若AB与CD相交:AB与CD呈十字交叉状
* 向量AC与AD异号
*
* 存在AB与CD呈 |- 形状,此时有向量AC与AD异号,但是不相交的情况
* 做向量CA和向量CB,此时向量同号
*
* 所以:
* 1.计算向量AC和向量AD的叉乘
* 2.计算向量CA和向量CB的叉乘
* 3.若以上两条计算结果同时满足叉乘 < 0, 则线条交叉
*/
function isLineCross(line1_start_point, line1_end_point, line2_start_point, line2_end_point) {
let line_AB = getPointVector(line1_start_point, line1_end_point)
let line_AC = getPointVector(line1_start_point, line2_start_point);
let line_AD = getPointVector(line1_start_point, line2_end_point);
let line_CD = getPointVector(line2_start_point, line2_end_point);
let line_CA = getPointVector(line2_start_point, line1_start_point);
let line_CB = getPointVector(line2_start_point, line1_end_point);
let AB_cross_AC = crossLine(line_AB, line_AC)
let AB_cross_AD = crossLine(line_AB, line_AD);
let CD_cross_CA = crossLine(line_CD, line_CA);
let CD_cross_CB = crossLine(line_CD, line_CB);
if (AB_cross_AC * AB_cross_AD < 0 && CD_cross_CA * CD_cross_CB < 0) {
return true;
} else {
return false;
}
}
// 检查图形有没有横穿
function checkPointCross(crossAllow = false) {
if (crossAllow) {
// 允许横穿,直接返回
return false;
}
if (pointList.length < 3) {
// 2条连续线条直接必不可能存在横穿现象
return false;
}
for (let i = 0; i < pointList.length - 2; i++) {
for (let j = i + 1; j < pointList.length; j++) {
let res = isLineCross(
pointList[i],
pointList[i + 1],
pointList[j],
pointList[(j + 1) % pointList.length]
);
if (res) {
return true;
}
}
}
return false;
}
</script>
</head>
<body onload="initCanvas('canvas-container', 'temp-canvas-container');">
<!-- 通过position让连个画布的HTML元素重叠 -->
<canvas id="canvas-container" style="position: absolute;"></canvas>
<canvas id="temp-canvas-container" style="position: absolute;"></canvas>
</body>
</html>
|