一、概览
基于网格的图形编辑组件:突出其基于网格背景,同时具备图形绘制、编辑(包括节点操作等)的功能。用户可以在网格背景下绘制不规则的图形元素,并对其进行各种编辑操作,方便创建复杂的图形结构。
二、预览
三、获取坐标的格式
[{"x":240,"y":135},{"x":240,"y":225},{"x":400,"y":225},{"x":400,"y":135},...]
四、功能介绍
结合前面画框功能及常见应用场景,为你详细列举该功能可能具备的特性:
- 基础绘制功能
-
- 自由绘制不规则形状:用户可通过鼠标或触摸操作,自由绘制任意形状的框,满足对复杂图形的绘制需求,如在图像标注场景中绘制物体的不规则轮廓。
-
- 支持多种绘制模式:除了直接绘制,还提供点选模式,用户点击多个点后自动连接形成多边形框;以及手绘模式,模拟真实笔触,让绘制更加自然流畅。
- 编辑功能
-
- 节点操作:绘制的框上显示可操作节点,用户能拖动节点改变框的形状,实现对已绘制图形的精细调整,适用于对标注精度要求较高的场景。
-
- 添加和删除节点:在绘制过程中或绘制完成后,用户可以随时添加新的节点来细化图形,也能删除不需要的节点,优化绘制的框。比如在绘制复杂的地图边界时,通过添加和删除节点来精确描绘边界形状。
-
- 整体移动和缩放:支持对整个绘制的框进行移动和缩放操作,方便调整框在画布中的位置和大小,以适应不同的需求。在可视化设计中,可通过整体移动和缩放画框来调整元素布局。
- 样式与属性设置
-
- 边框样式设置:用户可以自定义框的边框样式,包括线条颜色、粗细、虚线或实线等,增强视觉区分度。在图像编辑中,不同类型的标注可以使用不同样式的边框。
-
- 填充设置:为绘制的框添加填充颜色或透明度设置,使框内区域与背景区分开来,或者根据需要设置半透明效果,以便同时查看框内和框外的内容。在数据可视化中,填充不同颜色的框可以代表不同的数据类别。
- 交互功能
-
- 鼠标交互:通过鼠标的点击、拖动、右键操作实现绘制、选择、删除等功能。例如,点击开始绘制,拖动绘制线条,右键点击删除节点或整个框。
-
- 触摸交互(适用于移动设备):支持触摸操作,如触摸屏幕绘制、缩放和移动框,满足在移动设备上的使用需求。在移动办公或现场数据采集场景中,用户可以直接在平板或手机上进行操作。
-
- 快捷键操作:提供快捷键绑定,如使用特定的按键组合来快速完成常见操作,如撤销、重做、清除等,提高操作效率。对于频繁使用该功能的用户,快捷键操作能大大提升工作速度。
- 数据处理功能
-
- 获取坐标数据:能够获取绘制框的顶点坐标数据,这些数据可以用于后续的分析、处理或存储。在机器学习数据标注中,坐标数据是训练模型的重要依据。
-
- 数据导出与导入:支持将绘制的框及其相关属性数据导出为特定格式(如 JSON、XML),方便在不同系统或应用之间共享数据;同时也能导入已有的数据,实现数据的复用和编辑。在团队协作的标注项目中,数据的导出和导入功能可以方便不同标注员之间的数据交互。
- 辅助功能
-
- 网格背景:提供可切换的网格背景,帮助用户更准确地绘制和对齐图形。在绘制具有规则形状或需要精确位置的图形时,网格背景能起到很好的辅助作用。
-
- 撤销和重做:支持撤销上一步操作和重做已撤销的操作,让用户在绘制过程中可以随时纠正错误,避免从头开始绘制的麻烦。
-
- 清除与重置:可以清除当前绘制的所有框,或者重置整个绘制区域,回到初始状态,方便用户重新开始绘制。
五、适用于
摄像头区域侦测设置、摄像头预置位设置、区域画框等
六、代码实现,提供vue2、vue3、原生js
1、 vue3组件实现,父组件注册该组件即可使用
<template>
<!-- 使用传入的宽高动态设置 canvas 容器的样式 -->
<div
:style="{ width: `${canvasWidth}px`, height: `${canvasHeight}px` }"
class="canvas-container"
>
<!-- 用于绘制网格和不规则图形的 canvas 元素 -->
<canvas ref="gridCanvas"></canvas>
<div class="button-group">
<!-- 撤销上一步操作的按钮 -->
<button @click="undo">撤销</button>
<!-- 获取当前不规则图形各点坐标的按钮 -->
<button @click="getCoordinates">获取坐标</button>
<!-- 重置 canvas 内容的按钮 -->
<button @click="reset">重置</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from "vue";
// 定义组件接收的 props
const props = defineProps({
// canvas 容器的宽度,默认为 640
canvasWidth: {
type: Number,
default: 640,
},
// canvas 容器的高度,默认为 360
canvasHeight: {
type: Number,
default: 360,
},
// 初始的点数组,用于数据反显,默认为空数组
initialPoints: {
type: Array,
// default: () => [],
default: () => [
{ x: 240, y: 135 },
{ x: 240, y: 225 },
{ x: 400, y: 225 },
{ x: 400, y: 135 },
],
},
});
// 引用 canvas 元素
const gridCanvas = ref(null);
// 存储当前不规则图形的点数组,初始值为传入的初始点数据
const points = ref(props.initialPoints.slice());
// 标记是否正在绘制图形
const isDrawing = ref(false);
// 标记是否正在拖动某个点
const isDragging = ref(false);
// 当前正在拖动的点的索引
const draggedPointIndex = ref(-1);
// 网格的大小
const gridSize = ref(10);
// 画布内边距,这里设置为 0 以铺满整个 canvas
const framePadding = ref(0);
// canvas 的 2D 上下文对象
let ctx = null;
// 初始化 canvas 并绘制网格
const initCanvas = () => {
const canvas = gridCanvas.value;
// 设置 canvas 的宽度和高度
canvas.width = props.canvasWidth;
canvas.height = props.canvasHeight;
// 获取 canvas 的 2D 上下文对象
ctx = canvas.getContext("2d");
// 绘制网格
drawGrid();
};
// 绘制网格
const drawGrid = () => {
// 清空 canvas 内容
ctx.clearRect(0, 0, props.canvasWidth, props.canvasHeight);
// 设置网格线的颜色和宽度
ctx.strokeStyle = "rgba(0, 255, 0, 0.5)";
ctx.lineWidth = 1;
// 绘制垂直网格线
for (let x = 0; x < props.canvasWidth; x += gridSize.value) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, props.canvasHeight);
ctx.stroke();
}
// 绘制水平网格线
for (let y = 0; y < props.canvasHeight; y += gridSize.value) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(props.canvasWidth, y);
ctx.stroke();
}
};
// 处理鼠标按下事件
const handleMouseDown = (e) => {
// 限制鼠标点击的坐标在 canvas 范围内
const x = Math.min(Math.max(e.offsetX, 0), props.canvasWidth);
const y = Math.min(Math.max(e.offsetY, 0), props.canvasHeight);
const clickRadius = 5;
// 检查是否点击在某个点上
for (let i = 0; i < points.value.length; i++) {
const point = points.value[i];
if (Math.hypot(x - point.x, y - point.y) <= clickRadius) {
isDragging.value = true;
draggedPointIndex.value = i;
return;
}
}
// 若未点击在点上,则开始新的绘制
isDrawing.value = true;
points.value.push({ x, y });
// 绘制新的图形
drawShape();
};
// 处理鼠标移动事件
const handleMouseMove = (e) => {
// 限制鼠标移动的坐标在 canvas 范围内
const x = Math.min(Math.max(e.offsetX, 0), props.canvasWidth);
const y = Math.min(Math.max(e.offsetY, 0), props.canvasHeight);
if (isDrawing.value && points.value.length > 0) {
// 重新绘制 canvas
redraw();
// 绘制临时的图形(包含当前鼠标位置)
drawShape([...points.value, { x, y }]);
} else if (isDragging.value) {
// 更新被拖动点的坐标
points.value[draggedPointIndex.value] = { x, y };
// 重新绘制 canvas
redraw();
}
};
// 处理鼠标松开事件
const handleMouseUp = () => {
// 停止绘制和拖动状态
isDrawing.value = false;
isDragging.value = false;
draggedPointIndex.value = -1;
// 绘制最终的图形
drawShape();
};
// 处理鼠标右键点击事件
const handleContextMenu = (e) => {
// 阻止默认的右键菜单行为
e.preventDefault();
// 限制鼠标右键点击的坐标在 canvas 范围内
const x = Math.min(Math.max(e.offsetX, 0), props.canvasWidth);
const y = Math.min(Math.max(e.offsetY, 0), props.canvasHeight);
const clickRadius = 5;
// 检查是否右键点击在某个点上
for (let i = 0; i < points.value.length; i++) {
const point = points.value[i];
if (Math.hypot(x - point.x, y - point.y) <= clickRadius) {
// 移除该点
points.value.splice(i, 1);
// 重新绘制 canvas
redraw();
break;
}
}
};
// 绘制不规则图形
const drawShape = (pointsToDraw = points.value) => {
if (pointsToDraw.length < 2) return;
// 开始绘制路径
ctx.beginPath();
// 移动到第一个点
ctx.moveTo(pointsToDraw[0].x, pointsToDraw[0].y);
// 依次连接各个点
for (let i = 1; i < pointsToDraw.length; i++) {
ctx.lineTo(pointsToDraw[i].x, pointsToDraw[i].y);
}
// 闭合路径
ctx.closePath();
// 设置边框颜色和宽度
ctx.strokeStyle = "blue";
ctx.lineWidth = 2;
// 绘制边框
ctx.stroke();
if (pointsToDraw.length === points.value.length) {
// 设置填充颜色
ctx.fillStyle = "rgba(0, 0, 255, 0.1)";
// 填充图形
ctx.fill();
}
// 设置点的颜色
ctx.fillStyle = "red";
// 绘制各个点
pointsToDraw.forEach(({ x, y }) => {
ctx.beginPath();
ctx.arc(x, y, 5, 0, 2 * Math.PI);
ctx.fill();
});
};
// 重新绘制 canvas(先初始化,再绘制图形)
const redraw = () => {
initCanvas();
drawShape();
};
// 撤销上一步操作
const undo = () => {
if (points.value.length > 0) {
// 移除最后一个点
points.value.pop();
// 重新绘制 canvas
redraw();
}
};
// 获取当前不规则图形各点的坐标
const getCoordinates = () => {
console.log(
"不规则框坐标:",
points.value.map((p) => ({ x: p.x, y: p.y }))
);
};
// 重置 canvas 内容
const reset = () => {
// 清空点数组
// points.value = [];
points.value = [
{ x: 240, y: 135 },
{ x: 240, y: 225 },
{ x: 400, y: 225 },
{ x: 400, y: 135 },
];
// 重新绘制 canvas
redraw();
};
onMounted(() => {
// 初始化 canvas
initCanvas();
// 绘制初始的不规则图形
drawShape();
const canvas = gridCanvas.value;
// 绑定鼠标事件
canvas.addEventListener("mousedown", handleMouseDown);
canvas.addEventListener("mousemove", handleMouseMove);
canvas.addEventListener("mouseup", handleMouseUp);
canvas.addEventListener("contextmenu", handleContextMenu);
});
// 监听 canvas 宽高的变化,重新初始化 canvas 并绘制图形
watch([() => props.canvasWidth, () => props.canvasHeight], () => {
initCanvas();
drawShape();
});
// 监听初始点数据的变化,更新 points 数组,重新初始化 canvas 并绘制图形
watch(
() => props.initialPoints,
(newPoints) => {
points.value = newPoints.slice();
initCanvas();
drawShape();
}
);
// 导出函数以供外部调用,可以使用refs方法调用
defineExpose({
getCoordinates,
reset,
undo,
});
</script>
<style scoped>
.canvas-container {
position: relative;
margin: 20px auto;
border: 1px solid #ccc;
overflow: hidden;
}
canvas {
display: block;
width: 100%;
height: 100%;
background-color: #f0f0f0;
}
.button-group {
position: absolute;
top: 10px;
left: 10px;
z-index: 10;
}
button {
padding: 6px 12px;
font-size: 14px;
cursor: pointer;
background-color: #fff;
border: 1px solid #ddd;
margin-right: 8px;
}
</style>
2、vue2 组件实现,父组件注册该组件即可使用
<template>
<!-- 使用传入的宽高动态设置 canvas 容器的样式 -->
<div
:style="{ width: `${canvasWidth}px`, height: `${canvasHeight}px` }"
class="canvas-container"
>
<!-- 用于绘制网格和不规则图形的 canvas 元素 -->
<canvas ref="gridCanvas"></canvas>
<div class="button-group">
<!-- 撤销上一步操作的按钮 -->
<button @click="undo">撤销</button>
<!-- 获取当前不规则图形各点坐标的按钮 -->
<button @click="getCoordinates">获取坐标</button>
<!-- 重置 canvas 内容的按钮 -->
<button @click="reset">重置</button>
</div>
</div>
</template>
<script>
export default {
// 接收父组件传递的参数
props: {
// canvas 容器的宽度,默认为 640
canvasWidth: {
type: Number,
default: 640,
},
// canvas 容器的高度,默认为 360
canvasHeight: {
type: Number,
default: 360,
},
// 初始的点数组,用于数据反显,默认为空数组
initialPoints: {
type: Array,
// default: () => [],
default: () => [
{ x: 240, y: 135 },
{ x: 240, y: 225 },
{ x: 400, y: 225 },
{ x: 400, y: 135 },
],
},
},
data() {
return {
// 存储当前不规则图形的点数组
points: [],
// 标记是否正在绘制图形
isDrawing: false,
// 标记是否正在拖动某个点
isDragging: false,
// 当前正在拖动的点的索引
draggedPointIndex: -1,
// 网格的大小
gridSize: 10,
// 画布内边距,这里设置为 0 以铺满整个 canvas
framePadding: 0,
// canvas 的 2D 上下文对象
ctx: null,
};
},
mounted() {
// 将初始点数据赋值给 points 数组,实现数据反显
this.points = this.initialPoints.slice();
// 初始化 canvas
this.initCanvas();
// 绑定鼠标事件
this.bindEvents();
// 绘制初始的不规则图形
this.drawShape();
},
methods: {
// 初始化 canvas 并绘制网格
initCanvas() {
const canvas = this.$refs.gridCanvas;
// 设置 canvas 的宽度和高度
canvas.width = this.canvasWidth;
canvas.height = this.canvasHeight;
// 获取 canvas 的 2D 上下文对象
this.ctx = canvas.getContext("2d");
// 绘制网格
this.drawGrid();
},
// 绘制网格
drawGrid() {
const ctx = this.ctx;
// 清空 canvas 内容
ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
// 设置网格线的颜色和宽度
ctx.strokeStyle = "rgba(0, 255, 0, 0.5)";
ctx.lineWidth = 1;
// 绘制垂直网格线
for (let x = 0; x < this.canvasWidth; x += this.gridSize) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, this.canvasHeight);
ctx.stroke();
}
// 绘制水平网格线
for (let y = 0; y < this.canvasHeight; y += this.gridSize) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(this.canvasWidth, y);
ctx.stroke();
}
},
// 处理鼠标按下事件
handleMouseDown(e) {
// 限制鼠标点击的坐标在 canvas 范围内
const x = this.limitCoordinate(e.offsetX, this.canvasWidth);
const y = this.limitCoordinate(e.offsetY, this.canvasHeight);
const clickRadius = 5;
// 检查是否点击在某个点上
for (let i = 0; i < this.points.length; i++) {
const point = this.points[i];
if (this.calculateDistance(x, y, point.x, point.y) <= clickRadius) {
this.isDragging = true;
this.draggedPointIndex = i;
return;
}
}
// 若未点击在点上,则开始新的绘制
this.isDrawing = true;
this.points.push({ x, y });
// 绘制新的图形
this.drawShape();
},
// 处理鼠标移动事件
handleMouseMove(e) {
// 限制鼠标移动的坐标在 canvas 范围内
const x = this.limitCoordinate(e.offsetX, this.canvasWidth);
const y = this.limitCoordinate(e.offsetY, this.canvasHeight);
if (this.isDrawing && this.points.length > 0) {
// 重新绘制 canvas
this.redraw();
// 绘制临时的图形(包含当前鼠标位置)
this.drawShape([...this.points, { x, y }]);
} else if (this.isDragging) {
// 更新被拖动点的坐标
this.points[this.draggedPointIndex] = { x, y };
// 重新绘制 canvas
this.redraw();
}
},
// 处理鼠标松开事件
handleMouseUp() {
// 停止绘制和拖动状态
this.isDrawing = false;
this.isDragging = false;
this.draggedPointIndex = -1;
// 绘制最终的图形
this.drawShape();
},
// 处理鼠标右键点击事件
handleContextMenu(e) {
// 阻止默认的右键菜单行为
e.preventDefault();
// 限制鼠标右键点击的坐标在 canvas 范围内
const x = this.limitCoordinate(e.offsetX, this.canvasWidth);
const y = this.limitCoordinate(e.offsetY, this.canvasHeight);
const clickRadius = 5;
// 检查是否右键点击在某个点上
for (let i = 0; i < this.points.length; i++) {
const point = this.points[i];
if (this.calculateDistance(x, y, point.x, point.y) <= clickRadius) {
// 移除该点
this.points.splice(i, 1);
// 重新绘制 canvas
this.redraw();
break;
}
}
},
// 绘制不规则图形
drawShape(pointsToDraw = this.points) {
if (pointsToDraw.length < 2) return;
const ctx = this.ctx;
// 开始绘制路径
ctx.beginPath();
// 移动到第一个点
ctx.moveTo(pointsToDraw[0].x, pointsToDraw[0].y);
// 依次连接各个点
for (let i = 1; i < pointsToDraw.length; i++) {
ctx.lineTo(pointsToDraw[i].x, pointsToDraw[i].y);
}
// 闭合路径
ctx.closePath();
// 设置边框颜色和宽度
ctx.strokeStyle = "blue";
ctx.lineWidth = 2;
// 绘制边框
ctx.stroke();
if (pointsToDraw.length === this.points.length) {
// 设置填充颜色
ctx.fillStyle = "rgba(0, 0, 255, 0.1)";
// 填充图形
ctx.fill();
}
// 设置点的颜色
ctx.fillStyle = "red";
// 绘制各个点
pointsToDraw.forEach(({ x, y }) => {
ctx.beginPath();
ctx.arc(x, y, 5, 0, 2 * Math.PI);
ctx.fill();
});
},
// 重新绘制 canvas(先初始化,再绘制图形)
redraw() {
this.initCanvas();
this.drawShape();
},
// 撤销上一步操作
undo() {
if (this.points.length > 0) {
// 移除最后一个点
this.points.pop();
// 重新绘制 canvas
this.redraw();
}
},
// 获取当前不规则图形各点的坐标
getCoordinates() {
console.log(
"不规则框坐标:",
this.points.map((p) => ({ x: p.x, y: p.y }))
);
},
// 重置 canvas 内容
reset() {
// 清空点数组
// points.value = [];
points.value = [
{ x: 240, y: 135 },
{ x: 240, y: 225 },
{ x: 400, y: 225 },
{ x: 400, y: 135 },
];
// 重新绘制 canvas
this.redraw();
},
// 限制坐标在指定范围内
limitCoordinate(value, max) {
return Math.min(Math.max(value, 0), max);
},
// 计算两点之间的距离
calculateDistance(x1, y1, x2, y2) {
const dx = x1 - x2;
const dy = y1 - y2;
return Math.sqrt(dx * dx + dy * dy);
},
// 绑定鼠标事件
bindEvents() {
const canvas = this.$refs.gridCanvas;
canvas.addEventListener("mousedown", this.handleMouseDown);
canvas.addEventListener("mousemove", this.handleMouseMove);
canvas.addEventListener("mouseup", this.handleMouseUp);
canvas.addEventListener("contextmenu", this.handleContextMenu);
},
},
};
</script>
<style scoped>
.canvas-container {
position: relative;
margin: 20px auto;
border: 1px solid #ccc;
overflow: hidden;
}
canvas {
display: block;
width: 100%;
height: 100%;
background-color: #f0f0f0;
}
.button-group {
position: absolute;
top: 10px;
left: 10px;
z-index: 10;
}
button {
padding: 6px 12px;
font-size: 14px;
cursor: pointer;
background-color: #fff;
border: 1px solid #ddd;
margin-right: 8px;
}
</style>
3、原生js实现,在HTML文件里面运行即可使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Camera Grid Frame with Draggable, Deletable, Coordinate Retrieval and Reset</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
}
canvas {
border: 1px solid #ccc;
}
button {
position: absolute;
padding: 8px 16px;
font-size: 14px;
cursor: pointer;
}
#undoButton {
top: 20px;
left: 20px;
}
#getCoordinatesButton {
top: 20px;
left: 120px;
}
#resetButton {
top: 20px;
left: 240px;
}
</style>
</head>
<body>
<canvas id="gridCanvas" width="400" height="300"></canvas>
<button id="undoButton">撤销</button>
<button id="getCoordinatesButton">获取坐标</button>
<button id="resetButton">重置</button>
<script>
const canvas = document.getElementById('gridCanvas');
canvas.width = 640;
canvas.height = 360;
const ctx = canvas.getContext('2d');
const undoButton = document.getElementById('undoButton');
const getCoordinatesButton = document.getElementById('getCoordinatesButton');
const resetButton = document.getElementById('resetButton');
const gridSize = 10;
const framePadding = 0; // 边框内边距
const frameWidth = canvas.width - 2 * framePadding;
const frameHeight = canvas.height - 2 * framePadding;
// 预设的 points 数据,可根据需要修改
const initialPoints = [
{ x: 240, y: 135 },
{ x: 240, y: 225 },
{ x: 400, y: 225 },
{ x: 400, y: 135 },
];
const points = initialPoints.slice(); // 复制预设数据到 points
// 绘制方格
function drawGrid() {
for (let x = framePadding; x < framePadding + frameWidth; x += gridSize) {
for (let y = framePadding; y < framePadding + frameHeight; y += gridSize) {
ctx.strokeStyle = 'rgba(0, 255, 0, 0.5)';
ctx.lineWidth = 1;
ctx.strokeRect(x, y, gridSize, gridSize);
}
}
}
drawGrid();
let isDrawing = false;
let isDragging = false;
let draggedPointIndex = -1;
// 页面加载时绘制初始图形
if (points.length > 1) {
drawIrregularShape();
}
canvas.addEventListener('mousedown', (e) => {
const x = e.offsetX;
const y = e.offsetY;
const clickRadius = 5;
// 检查是否点击在节点上
for (let i = 0; i < points.length; i++) {
const point = points[i];
const dx = x - point.x;
const dy = y - point.y;
if (Math.sqrt(dx * dx + dy * dy) <= clickRadius) {
isDragging = true;
draggedPointIndex = i;
return;
}
}
// 若未点击在节点上,则开始新的绘制
isDrawing = true;
points.push({ x, y });
drawIrregularShape();
});
canvas.addEventListener('mousemove', (e) => {
const x = e.offsetX;
const y = e.offsetY;
if (isDrawing && points.length > 0) {
redrawCanvas();
drawIrregularShape([...points, { x, y }]);
} else if (isDragging) {
points[draggedPointIndex] = { x, y };
redrawCanvas();
drawIrregularShape();
}
});
canvas.addEventListener('mouseup', () => {
isDrawing = false;
isDragging = false;
draggedPointIndex = -1;
});
canvas.addEventListener('contextmenu', (e) => {
e.preventDefault();
const x = e.offsetX;
const y = e.offsetY;
const clickRadius = 5;
for (let i = 0; i < points.length; i++) {
const point = points[i];
const dx = x - point.x;
const dy = y - point.y;
if (Math.sqrt(dx * dx + dy * dy) <= clickRadius) {
points.splice(i, 1);
redrawCanvas();
drawIrregularShape();
break;
}
}
});
undoButton.addEventListener('click', () => {
if (points.length > 0) {
points.pop();
redrawCanvas();
drawIrregularShape();
}
});
getCoordinatesButton.addEventListener('click', () => {
const coordinates = points.map(point => ({ x: point.x, y: point.y }));
console.log('不规则框各点位的坐标:', coordinates);
alert('不规则框各点位的坐标:' + JSON.stringify(coordinates));
// 你可以在这里将坐标信息用于其他用途,比如发送到服务器等
});
resetButton.addEventListener('click', () => {
points.length = 0;
redrawCanvas();
});
function redrawCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawGrid();
}
function drawIrregularShape(pointsToDraw = points) {
if (pointsToDraw.length > 1) {
ctx.beginPath();
ctx.moveTo(pointsToDraw[0].x, pointsToDraw[0].y);
for (let i = 1; i < pointsToDraw.length; i++) {
ctx.lineTo(pointsToDraw[i].x, pointsToDraw[i].y);
}
ctx.closePath();
ctx.strokeStyle = 'blue';
ctx.lineWidth = 2;
ctx.stroke();
if (pointsToDraw.length === points.length) {
ctx.fillStyle = 'rgba(0, 0, 255, 0.1)';
ctx.fill();
}
// 绘制节点
const nodeRadius = 5;
ctx.fillStyle = 'red';
for (let i = 0; i < pointsToDraw.length; i++) {
const point = pointsToDraw[i];
ctx.beginPath();
ctx.arc(point.x, point.y, nodeRadius, 0, 2 * Math.PI);
ctx.fill();
}
}
}
</script>
</body>
</html>