多边形绘图,纯js实现(cv可用),可以自选绘图区域颜色,展示文字。绘图完成后可以拖拽多边形点位实现区域的修改(引用的话,创建html页面及可用,中间就用到了layui的弹出层,需要引用layui的js、css,官网直接可下,也可以自行修改,另一个是在线引用。
- 上代码
Canvas绘图中只有一个元素-canvas,所以实现多边形的拖拽是非常麻烦的,方法1、只能判断你点击的地方为圆心,给个差不多的半径的圆的范围是否包含某个点,然后达成修改多边形。 方法2、就是判断离鼠标点击的地方距离最近的一个点
这是绘画好的多边形区域(canvas是底板,图片是给canvas的背景图片):
这是拖拽、拉伸点位实现的更改多边形区域:
上代码
绘画完区域后 Enter 是结束当前绘画事件(可更改成其他事件结束当前绘画),主要功能代码是在代码下部分showGraphical() ,都有注释。可能循环较多,因为后来部分功能不需要,有一些多余代码(可删)。
不懂的属性官网都有: canvas属性.
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<title></title>
<link rel="stylesheet" href="./layui/css/layui.css">
<style>
/*谷歌去除滚动条*/
::-webkit-scrollbar {
display: none;
}
html,
body {
/*隐藏滚动条,当IE下溢出,仍然可以滚动*/
-ms-overflow-style: none;
/*火狐下隐藏滚动条*/
overflow: -moz-scrollbars-none;
}
.layui-btn {
background-color: cornflowerblue;
color: white;
text-overflow: inherit;
}
#mop2 {
margin-top: 15px;
}
/*更改颜色弹出层右上角关闭隐藏*/
.layui-layer-setwin {
display: none;
}
/*下方操作按钮*/
#bottom {
text-align: center;
margin-top: 30px;
}
</style>
</head>
<body>
<canvas id="canvas" onclick="" style='background-color: #F0F8FF' width="900" height="515"></canvas>
<div id="bottom">
<input type="button" class="layui-btn oder" value="清空" onclick="ClearCanvas()" />
<input type="button" class="layui-btn oder" value="保存" onclick="Success()" />
<input type="button" class="layui-btn oder" value="显示" onclick="showGraphical()" />
<input type="button" class="layui-btn oder" value="隐藏" onclick="HideCanvas()" />
</div>
<div style="display: none;" id="mop2">
<div class="layui-form-item">
<label class="layui-form-label">标签:</label>
<div class="layui-input-inline">
<select class="layui-input" name="biaoqian" id="biaoqian">
<option value="">请选择标签</option>
<option value="测试标签">我是一个标签</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">选择颜色</label>
<div class="layui-input-inline">
<select class="layui-input" name="yanse" id="yanse" onchange="changeColor(this.value)">
<option value="">请选择区域颜色</option>
<option value="黑">黑</option>
<option value="灰">灰</option>
<option value="白">白</option>
<option value="红">红</option>
<option value="橙">橙</option>
<option value="黄">黄</option>
<option value="绿">绿</option>
<option value="青">青</option>
<option value="蓝">蓝</option>
<option value="紫">紫</option>
</select>
</div>
</div>
<div class="layui-form-item">
<div class="layui-input-block">
<input type="button" onclick="UnitOut()" style="width: 9.9vw;" value="保存" class="layui-btn">
</div>
</div>
</div>
</body>
</html>
<script src="./layui/layui.js"></script>
<script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
<!--方案1-->
<script>
var Unocolor = null;//选择的颜色
$(function () {
bindImg();
//拾色器渲染
layui.use('form', function () {
var form = layui.form;
//常规使用
form.render();
});
})
function changeColor(e) {
Unocolor = e;
}
//绑定区域图片
function bindImg() {
$("#canvas").css("background", `url(/images/11.png) center center no-repeat`)
$("#canvas").css("background-color", `#F0F8FF`)
//$.ajax({
// url: 接口,
// data: 值,
// type: "post",
// success: function (res) {
// console.log(res);
// $("#canvas").css("background", `url(${res}) center center no-repeat`)
// $("#canvas").css("background-color", `#F0F8FF`)
// //$("#canvas").css("background-size", `cover`)
// }
//})
}
const canvas = document.getElementById("canvas")
const context = canvas.getContext("2d")
const COLOR = "#1890ff"
//线的粗细
const LINEWIDTH = 2
//圆点半径
const DOTRADIUS = 3
//点颜色
context.fillStyle = COLOR
//线颜色
context.strokeStyle = COLOR
context.lineWidth = LINEWIDTH
//单个实时获取图形的值
let points = []
//获取到所有图形值的组
let ListPoints = [];
//是否继续操作
let isDrawing = false
//是否删除数组末尾最后一个
let isPointAdd = false
//(全局)创建的图形的索引
var GraphicIndex = 0;
//计算位置
function windowToCanvas(x, y) {
var bbox = canvas.getBoundingClientRect();
return {
x: x - bbox.left * (canvas.width / bbox.width),
y: y - bbox.top * (canvas.height / bbox.height)
};
}
//绘制圆点
function drawDot(loc) {
context.beginPath()
context.arc(loc.x, loc.y, DOTRADIUS * 2, 0, 2 * Math.PI);
context.fill()
}
//绘制线
function drawLine(locStart, locEnd) {
context.beginPath()
context.moveTo(locStart.x, locStart.y);
context.lineTo(locEnd.x, locEnd.y);
context.stroke();
}
//绘制图案
function drawPolygon(points) {
context.clearRect(0, 0, canvas.width, canvas.height);
const length = points.length
for (let i = 0; i < parseInt(length / 2); i++) {
drawDot(points[i * 2])
drawLine(points[i * 2], points[i * 2 + 1])
}
drawDot(points[length - 1])
//以圆点为中心划分区域
drawLine(points[length - 1], points[0])
}
//触发左键
canvas.addEventListener("mousedown", e => {
isDrawing = true;
for (var i = 0; i < ListPoints.length; i++) {
var singlePoints = ListPoints[i];
for (var j = 0; j < singlePoints.length; j++) {
if (singlePoints[j].show) {
var dis = Math.sqrt((e.clientX - singlePoints[j].x) * (e.clientX - singlePoints[j].x) + (e.clientY - singlePoints[j].y) * (e.clientY - singlePoints[j].y));//Math.sqrt()求平方跟 为了判断鼠标点击后以鼠标点击的位置为中心画个圆,圆内是否包含的有显示的点,有的话就移动
if (dis <= 20) {
singlePoints[j]['update'] = true;//要修改的点位加上update=true
//IsClear = true;
}
isDrawing = false
}
}
}
if (isDrawing) {
const loc = windowToCanvas(e.clientX, e.clientY)
points.push(loc)
if (points.length > 1) {
if (points[points.length - 1].x == points[points.length - 3].x && points[points.length - 1].y == points[points.length - 3].y) {
points.pop();//防止用户在同一坐标点多次点击导致的bug,如果相同,就移除数组的最后一项坐标
}
else {
drawPolygon(points)
}
}
else {
drawPolygon(points)
}
isDrawing = true
isPointAdd = true
}
})
//鼠标松开事件
canvas.addEventListener("mouseup", e => {
var istrue = false;
for (var i = 0; i < ListPoints.length; i++) {
var singlePoints = ListPoints[i];
for (var j = 0; j < singlePoints.length; j++) {
if (singlePoints[j].update) {
singlePoints[j].x = e.clientX;
singlePoints[j].y = e.clientY;
istrue = true;
}
}
if (istrue) {
showGraphical();
for (var j = 0; j < singlePoints.length; j++) {
if (singlePoints[j].update) {
delete singlePoints[j].update;
}
}
}
}
})
//移动触发
canvas.addEventListener("mousemove", e => {
if (isDrawing) {
const loc = windowToCanvas(e.clientX, e.clientY)
if (!isPointAdd) {
points.pop()
}
points.push(loc)
isPointAdd = false
if (points.length > 2) {
if (points[points.length - 1].x == points[points.length - 3].x && points[points.length - 1].y == points[points.length - 3].y) {
points.pop();//防止用户在同一坐标点多次点击导致的bug,如果相同,就移除数组的最后一项坐标
}
else {
drawPolygon(points)
}
}
else {
drawPolygon(points)
}
}
else {
var istrue = false;
for (var i = 0; i < ListPoints.length; i++) {
var singlePoints = ListPoints[i];
for (var j = 0; j < singlePoints.length; j++) {
if (singlePoints[j].update) {
singlePoints[j].x = e.clientX;
singlePoints[j].y = e.clientY;
istrue = true;
}
}
if (istrue) {
showGraphical();
}
}
}
})
//Enter事件监听
$(document).keydown(function (event) {
console.log("当前绘画区域的点位信息:");
console.log(points)
if (event.keyCode == 13) {
if (isDrawing) {
updateColor()
}
}
else if (event.keyCode == 3) {
console.log(2222222)
}
});
//鼠标右键事件监听
$("#canvas").oncontextmenu = function (e) {
e.preventDefault();
// 执行代码块
console.log(158585858)
console.log(this)
}
//标签更改保存事件
function UnitOut() {
layui.use('layer', function () {
var layer = layui.layer;
var bq = $("#biaoqian option:selected").val()
var ys = $("#yanse option:selected").val()
if (bq == "" || ys == "") {
layer.msg("请全部完成后再试!", { icon: 5 })
return false;
}
if (bq.length > 0 && Unocolor.length > 0) {
drawPolygon(points)
bindColorRGB(Unocolor)
for (var i = 0; i < points.length; i++) {
spotAdd = points[i];
spotAdd['Index'] = GraphicIndex;
spotAdd['show'] = false;
spotAdd['color'] = Unocolor;
spotAdd['lable'] = $("#biaoqian option:selected").val();
spotAdd['min'] = min;
spotAdd['max'] = max;
}
GraphicIndex++;
ListPoints.push(points)
var arrResult = [];
for (var m = 0; m < ListPoints.length; m++) {
去除重复点
var arrTemp = [];
var sum = 0;
var singlePoints = ListPoints[m];
for (var i = 0; i < singlePoints.length; i++) {
if (i < singlePoints.length - 1 && singlePoints.length != 1) {
if (singlePoints[i].x != singlePoints[i + 1].x || singlePoints[i].y != singlePoints[i + 1].y) {
sum = 0;
}
}
else {
sum = 0;
}
for (var j = 0; j < singlePoints.length; j++) {
if (singlePoints[i].x == singlePoints[j].x && singlePoints[i].y == singlePoints[j].y) {
sum++;
}
}
if (sum <= 2) {
arrTemp.push(singlePoints[i])
}
}
arrResult.push(arrTemp)
}
ListPoints = arrResult;
points = [];
isDrawing = false;
isPointAdd = false;
layer.close(indexLayui);
}
else {
layer.msg("请全部完成后再试!", { icon: 5 })
}
})
}
var min = "", max = "", colorRGB = "";
//颜色最终赋值
function bindColorRGB(colorval) {
switch (colorval) {
case '黑': min = "0,0,0"; max = "180,255,46"; colorRGB = "RGB(0, 0, 0, 0.3)";
break;
case '灰': min = "0,0,46"; max = "180,43,220"; colorRGB = "RGB(163, 155, 153, 0.3)";
break;
case '白': min = "0,0,221"; max = "180,30,255"; colorRGB = "RGB(255, 255, 255, 0.3)";
break;
case '红': min = "0,43,46"; max = "10,255,255"; colorRGB = "RGB(232, 28, 28, 0.3)";
break;
case '橙': min = "11,43,46"; max = "25,255,255"; colorRGB = "RGB(237, 160, 59, 0.3)";
break;
case '黄': min = "26,43,46"; max = "34,255,255"; colorRGB = "RGB(255, 255, 0, 0.3)";
break;
case '绿': min = "35,43,46"; max = "77,255,255"; colorRGB = "RGB(28, 230, 31, 0.3)";
break;
case '青': min = "78,43,46"; max = "99,255,255"; colorRGB = "RGB(18, 206, 235, 0.3)";
break;
case '蓝': min = "100,43,46"; max = "124,255,255"; colorRGB = "RGB(17, 58, 242, 0.3)";
break;
case '紫': min = "125,43,46"; max = "155,255,255"; colorRGB = "RGB(87, 5, 252, 0.3)";
break;
}
}
//修改颜色
var indexLayui = null;
function updateColor() {
layui.use('layer', function () {
var layer = layui.layer;
indexLayui = layer.open({
type: 1,
title: '添加标签',
area: ['380px', '250px'],
content: $("#mop2"),
end: function () {
$("#mop2").hide();
},
});
});
}
//显示区域
var xcenter = 0;//中心x轴
var ycenter = 0;//中心y轴
function showGraphical() {
context.clearRect(0, 0, 900, 515);//清空整个画布 900 515 画布宽高
console.log("展示中图形的点位信息:")
console.log(ListPoints)
for (var i = 0; i < ListPoints.length; i++) {
//SingleGraphicShow(ListPoints[i]);
var SingleArrPoint = ListPoints[i];
//var temparr = [{ x: 1, y: 6 }, { x: 9, y: 6 }, { x: 2, y: 1 }, { x: 5, y: 9 }, {x:1,y:6}]
SingleArea(SingleArrPoint);
xcenter = 0;//中心x轴
ycenter = 0;//中心y轴
var cc = document.getElementById("canvas");
var canvasNew = cc.getContext("2d");
canvasNew.beginPath();
//点色
//canvasNew.fillStyle = ""
canvasNew.lineWidth = 2 //线的粗细
canvasNew.beginPath(); //开始
var lableTemp = "";
for (var j = 0; j < SingleArrPoint.length; j++) {
if (j == 0) {
canvasNew.moveTo(SingleArrPoint[0].x, SingleArrPoint[0].y);//起始结束点
}
else {
canvasNew.lineTo(SingleArrPoint[j].x, SingleArrPoint[j].y);
}
xcenter += SingleArrPoint[j].x;
ycenter += SingleArrPoint[j].y;
SingleArrPoint[j].show = true;
lableTemp = SingleArrPoint[j].lable;
bindColorRGB(SingleArrPoint[j].color)
}
canvasNew.closePath();
canvasNew.strokeStyle = colorRGB; //绘制好后线的颜色
canvasNew.stroke(); // 进行绘制绘图
canvasNew.fillStyle = colorRGB;//填充的颜色和透明度
canvasNew.fill();//填充颜色
canvasNew.font = "20px Georgia";//字体样式
canvasNew.fillStyle = "white";//填充的颜色和透明度
canvasNew.fillText(lableTemp, xcenter / SingleArrPoint.length, ycenter / SingleArrPoint.length);//文字内容和位置
}
}
//清空画布包括之前的点位(初始化画布)
function ClearCanvas() {
context.clearRect(0, 0, 900, 515);//清空整个画布 900 515 画布宽高
ListPoints = [];
points = [];
const COLOR = "#1890ff"
//线的粗细
const LINEWIDTH = 2
//圆点半径
const DOTRADIUS = 3
//点颜色
context.fillStyle = COLOR
//线颜色
context.strokeStyle = COLOR
context.lineWidth = LINEWIDTH
}
//隐藏画布图形(只是隐藏图形未清空数组点位)
function HideCanvas() {
context.clearRect(0, 0, 900, 515);//清空整个画布 900 515 画布宽高
for (var i = 0; i < ListPoints.length; i++) {
var SingleArrPoint = ListPoints[i];
for (var j = 0; j < SingleArrPoint.length; j++) {
SingleArrPoint[j].show = false;
}
}
const COLOR = "#1890ff"
//线的粗细
const LINEWIDTH = 2
//圆点半径
const DOTRADIUS = 3
//点颜色
context.fillStyle = COLOR
//线颜色
context.strokeStyle = COLOR
context.lineWidth = LINEWIDTH
}
//封装显示单个图形(可循环调用)
function SingleGraphicShow(SingleArrPoint) {
context.clearRect(0, 0, 900, 515);//清空整个画布 900 515 画布宽高
for (var i = 0; i < length; i++) {
var SingleTemp = ListPoints[i];
for (var j = 0; j < SingleTemp.length; j++) {
SingleArrPoint[j].show = false;
}
}//清空所有数组需将数组内的show(是否在显示)属性设置为false
xcenter = 0;//中心x轴
ycenter = 0;//中心y轴
var cc = document.getElementById("canvas");
var canvasNew = cc.getContext("2d");
canvasNew.beginPath();
//点色
//canvasNew.fillStyle = ""
//线色
canvasNew.lineWidth = 2 //线的粗细
canvasNew.beginPath(); //开始
for (var j = 0; j < SingleArrPoint.length; j++) {
if (j == 0) {
canvasNew.moveTo(SingleArrPoint[0].x, SingleArrPoint[0].y);//起始结束点
} else {
canvasNew.lineTo(SingleArrPoint[j].x, SingleArrPoint[j].y);
}
xcenter += SingleArrPoint[j].x;
ycenter += SingleArrPoint[j].y;
SingleArrPoint[j].show = true;
}
canvasNew.closePath();
canvasNew.strokeStyle = "#DC143C"; //绘制好后线的颜色
canvasNew.stroke(); // 进行绘制绘图
canvasNew.fillStyle = "rgb(220,20,60,0.3)";//填充的颜色和透明度
canvasNew.fill();//填充颜色
canvasNew.font = "20px Georgia";//字体样式
canvasNew.fillStyle = "black";//填充的颜色和透明度
canvasNew.fillText("China!", xcenter / SingleArrPoint.length, ycenter / SingleArrPoint.length);//文字内容和位置
}
//将数据转换成最终后台需要的上传格式
var resultPoints = [];
function ChangeValue() {
for (var i = 0; i < ListPoints.length; i++) {
var SingleArrPoint = ListPoints[i];
var arrx = [],
arry = [],
name = "",
color = "",
max = "",
min = "";
for (var j = 0; j < SingleArrPoint.length; j++) {
arrx.push(SingleArrPoint[j].x);
arry.push(SingleArrPoint[j].y);
name = SingleArrPoint[j].lable;
color = SingleArrPoint[j].color;
max = SingleArrPoint[j].max;
min = SingleArrPoint[j].min;
}
resultPoints.push({ x: arrx, y: arry, name: name, color: color, max: max, min: min });
}
console.log(resultPoints);
}
//求单个不规则图形的面积 公式: sum+=0.5×数组[i].x(数组[i+1].y-数组[i+1].y) i:数组的索引下标 x,y为横纵坐标
function SingleArea(SingleArrPoint) {
var sum = 0;
for (var i = 0; i < SingleArrPoint.length; i++) {
if (i == 0) {
sum += SingleArrPoint[i].x * (SingleArrPoint[i + 1].y - SingleArrPoint[SingleArrPoint.length - 1].y);
}
else if (i == SingleArrPoint.length - 1) {
sum += SingleArrPoint[i].x * (SingleArrPoint[0].y - SingleArrPoint[i - 1].y);
}
else {
sum += SingleArrPoint[i].x * (SingleArrPoint[i + 1].y - SingleArrPoint[i - 1].y)
}
}
console.log("图形面积为:" + Math.abs(sum / 2));
}
//layui关闭弹出层
function closeUser() {
var index1 = parent.layer.getFrameIndex(window.name); //先得到当前iframe层的索引
parent.layer.close(index1); //再执行关闭
}
//16进制转rgb
//使用方法:
//"#fff".colorRgb(); // rgb(255,255,255)
//"#ffffff".colorRgb(); // rgb(255,255,255)
String.prototype.colorRgb = function () {
// 16进制颜色值的正则
var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
// 把颜色值变成小写
var color = this.toLowerCase();
if (reg.test(color)) {
// 如果只有三位的值,需变成六位,如:#fff => #ffffff
if (color.length === 4) {
var colorNew = "#";
for (var i = 1; i < 4; i += 1) {
colorNew += color.slice(i, i + 1).concat(color.slice(i, i + 1));
}
color = colorNew;
}
// 处理六位的颜色值,转为RGB
var colorChange = [];
for (var i = 1; i < 7; i += 2) {
colorChange.push(parseInt("0x" + color.slice(i, i + 2)));
}
return "RGB(" + colorChange.join(",") + ",0.3)";
} else {
return color;
}
};
</script>
注:此功能最好放到一个跳转页面,因为其他样式可能会导致canvas的 x,y的坐标偏移,导致绘画好区域后拖拽寻找点位偏移较大。
里面还有个计算绘制好的多边形面积的,因为功能后来不需要了,只做了简单图形的面积验证没问题,好像是只要没有绘制的线相交,面积就没问题。