在Canvas中用鼠标绘制多边形区域
一 需求场景
需要在监控画面中绘制一块监测区域(多边形),最后取多边形的各个坐标点。
- 鼠标左键:加入路径点
- 鼠标右键:撤销
- 鼠标移动:动态绘制区域
- 双击鼠标左键:清除画布
- 回车:完成绘图
二 效果图
三 Canvas基本概念
如果你对canvas不熟悉,不妨简单过一下概念,以便理解代码。
1. 坐标系
2. 创建canvas与初始化画布
<canvas
id="myCanvas"
style="background-color: yellow"
width="400"
height="400"
tabindex="0"
></canvas>
<script>
// 获取元素
const el=document.getElementById('myCanvas')
// 创建画布
const ctx=el.getContext('2d')
</script>
3. 绘制直线路径
ctx.beginPath() // 路径开始
ctx.moveTo(100,100) // 鼠标的起点位置
ctx.lineTo(100,200) // 从上个点出发,连线至(100,200)
ctx.stroke() // 描线结束
4. 设置样式
ctx.beginPath()
ctx.rect(100,100,200,200) // 绘制矩形
ctx.lineWidth=7 // 边框宽度
ctx.strokeStyle='blue' // 边框颜色
ctx.fillStyle='red' // 填充背景色,需要注意前后顺序
ctx.fill() // 填充
ctx.stroke() // 描线
四 事件属性
绘制区域的过程中,用到了鼠标点击事件等。我们需要鼠标的位置信息,那么就引出了以下容易混淆的概念,简单列一下:
el.addEventListener('click', (e)=>{
e.clientX // 鼠标相对于客户端窗口的水平坐标位置
e.offsetX // 鼠标与目标节点的边在X轴的偏移量
e.x //`clientX`的别名
e.movementX // 两个事件间水平移动的距离
e.pageX // 相对于整个文档的 x坐标值(像素)
e.screenX // 鼠标在屏幕的水平坐标
});
这里需要的属性是:e.offsetX, e.offsetY
五 实现步骤
1. 初始化画布
<canvas
id="myCanvas"
style="background-color: yellow"
width="400"
height="400"
tabindex="0"
></canvas>
<script>
// canvas标签
const el = document.getElementById('myCanvas');
const w = el.width;
const h = el.height;
// 初始化画布
const ctx = el.getContext('2d');
ctx.lineWidth = 4;
ctx.strokeStyle = '#0073e6';
ctx.fillStyle = 'rgba(0, 115, 230,0.2)';
const points = []; // 路径点位
let isPaint = true; // 是否处于绘制状态,完成绘制时会用到
</script>
2. 加入路径点位
通过click
事件加入坐标点:
const onClick = (e) => {
points.push({ x: e.offsetX, y: e.offsetY });
};
el.addEventListener('click', onClick);
3. 实时绘制
当加入了第一个点位之后,就要根据鼠标移动的位置实现动态绘制,这里用到了mousemove
事件。
(1) 当points
中有一个点时,它将与鼠标移动的位置形成一条直线;
(2) 当points
中有两个及以上的点时,这些点将与鼠标移动的位置形成一个区域
const onMouseMove = (e) => {
draw({ x: e.offsetX, y: e.offsetY });
};
el.addEventListener('mousemove', onMouseMove);
根据坐标点数量情况采取不同的绘制方式:
const draw = (mouse) => {
const pointLength = points.length;
if (isPaint === false) return; // 非绘制状态下不用绘制
switch (pointLength) {
case 0:
return; // 没有点,不用绘制
case 1:
drawLine(mouse); // 画直线
break;
default:
drawArea(mouse); // 画区域
break;
}
};
// 绘制直线
const drawLine = (mouse) => {
ctx.clearRect(0, 0, w, h); // 每次绘制前都要清空一下画布
const firstPoint = points[0];
ctx.beginPath();
ctx.moveTo(firstPoint.x, firstPoint.y);
ctx.lineTo(mouse.x, mouse.y);
ctx.stroke();
};
// 绘制区域
const drawArea = (mouse) => {
ctx.clearRect(0, 0, w, h);
ctx.beginPath();
for (let i = 0; i < points.length; i++) {
if (i === 0) {
ctx.moveTo(points[i].x, points[i].y); // 第一个点用的moveTo
} else {
ctx.lineTo(points[i].x, points[i].y); // 其余点用lineTo
}
}
// 如果传入了鼠标点,将它作为一个临时点,作为points中的最后一个点;
// 否则只需将point数组中的点连接即可
if (mouse) {
ctx.lineTo(mouse.x, mouse.y);
}
// 画回第一个点,封闭区域
ctx.lineTo(points[0].x, points[0].y);
// 划线与填充
ctx.stroke();
ctx.fill();
};
4. 撤销点位
监听鼠标右键,这里需要注意阻止默认事件
el.addEventListener('contextmenu', onRightClick);
const onRightClick = (e) => {
// 防止打开菜单菜单
e.stopPropagation();
e.preventDefault();
// 如果没有点 不用撤销
if(points.length===0) return
points.pop() // 移除最后一个点
// 移除点后需要重新绘制
draw({ x: e.offsetX, y: e.offsetY })
return false
};
5. 回车完成绘制
el.addEventListener('keydown', onEnter);
const onEnter = (e) => {
let keyCode = e.keyCode || e.which || e.charCode;
if (keyCode == 13) {
const pointLength = points.length;
// 至少需要三个点才允许画区域
if (pointLength >= 3) {
isPaint = false; // 停止绘制
drawArea(); // 画封闭区域
}
}
};
需要注意的是,这里要把canvas标签的tabindex
设为0,否则回车将不会生效。(tabindex用来定位html元素,即使用tab键时焦点的顺序)
6. 清空画布
el.addEventListener('dblclick', onClear);
const onClear = () => {
// 清空画布意味着要重新开始绘制了,所以将绘制状态打开,并清空路径点位
if (isPaint === false) {
isPaint = true;
}
points = [];
ctx.clearRect(0, 0, w, h);
};
源码与封装
https://gitee.com/chenxiangzhi/use-draw
我这里封装了useDraw
那么如何将它用在实际场景中呢?我在里面简单封装了,使用方式如下,只需要传入Dom元素(将会创建一个等宽等高的canvas覆在Dom元素上方)和一些选项即可
import useDraw from './useDraw';
let draw=useDraw()
draw.init(domRef, {
// 传入一些选项。
// 绘制完成时的回调
onComplete: (points) => {
console.log('points', points);
}
});
// 用完后记得销毁
draw.destroy()
本文作者:sanhuamao
本文链接:https://www.cnblogs.com/sanhuamao/p/17648907.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步