大体思路:
三层canvas,图片canvas、绘制canvas和绘制区域保存canvas。
canvas画布大小根据加载的图片大小按比例适配至页面操作区域。
submitData为后端数据存储格式,可根据要求适当调整。
** 效果图 **
```<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<style>
.tool-box {
width: 60vw;
height: 40px;
padding: 5px 30px;
margin: 20px auto 0;
box-sizing: border-box;
text-align: right;
}
.canvas-wrap {
width: 60vw;
height: 33.75vw;
margin: 0px auto;
background-color: #000;
border: 3px;
border-color: #333;
position: relative;
}
#imgCanvas,
#drawCanvas,
#saveCanvas {
background: rgba(255, 0, 255, 0);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
#drawCanvas {
z-index: 2;
}
</style>
<body>
<div>
<div class="tool-box">
<button size="mini" :type="isDrawing ? 'warning' : 'primary'" id="startDraw">绘制区域</button>
<button size="mini" type="danger" :disabled="isDrawing" id="clearAll">全部清除</button>
<button size="mini" type="success" :disabled="isDrawing" id="savePoints">保存</button>
</div>
<div class="canvas-wrap">
<canvas id="imgCanvas" ref="canvaxbox"></canvas>
<!--用来和鼠标进行交互操作的canvas-->
<canvas id="drawCanvas" ref="canvas" :style="{cursor: isDrawing?'crosshair': 'default'}"> </canvas>
<!--存储已生成的点线,避免被清空-->
<canvas id="saveCanvas" ref="canvasSave"></canvas>
</div>
</div>
</body>
<script>
document.getElementById("clearAll").addEventListener("click", () => {
func.clearAll();
})
document.getElementById("startDraw").addEventListener("click", () => {
func.startDraw();
})
document.getElementById("savePoints").addEventListener("click", () => {
func.savePoints();
})
var func = {
imgUrl: 'img/code/wechat-code.jpg',
isDrawing: false, // 是否正在绘制
ratio: 1,
imgWidth: 3020,
imgHeight: 1080,
wrapWidth: 300,
wrapHeight: 300,
canvasWidth: 300,
canvasHeight: 300,
drawingPoints: [],
drawedPoints: [],
imgCanvas: null,
imgCtx: null,
drawCanvas: null,
drawCtx: null,
saveCanvas: null,
saveCtx: null,
submitData: [
// { "polygon": { "x1": 700, "y1": 273, "x2": 975, "y2": 278, "x3": 1107, "y3": 368, "x4": 718, "y4": 354 } },
// { "polygon": { "x1": 49, "y1": 32, "x2": 183, "y2": 35, "x3": 181, "y3": 100, "x4": 55, "y4": 97 } },
// { "polygon": { "x1": 433, "y1": 250, "x2": 706, "y2": 253, "x3": 707, "y3": 392, "x4": 435, "y4": 393 } },
// { "polygon": { "x1": 45, "y1": 539, "x2": 193, "y2": 538, "x3": 192, "y3": 622, "x4": 41, "y4": 623, "x5": 42, "y5": 623 } }
],
initCanvas: function () { // 初始化canvas画布
let canvasWrap = document.getElementsByClassName('canvas-wrap')
this.wrapWidth = canvasWrap[0].clientWidth
this.wrapHeight = canvasWrap[0].clientHeight
this.imgCanvas = document.getElementById('imgCanvas')
this.imgCtx = imgCanvas.getContext('2d')
// 绘制canvas
this.drawCanvas = document.getElementById('drawCanvas')
this.drawCtx = drawCanvas.getContext('2d')
// 保存绘制区域 saveCanvas
this.saveCanvas = document.getElementById('saveCanvas')
this.saveCtx = saveCanvas.getContext('2d')
},
initImgCanvas: function () {
// 计算宽高比
let ww = this.wrapWidth // 画布宽度
let wh = this.wrapHeight // 画布高度
let iw = this.imgWidth // 图片宽度
let ih = this.imgHeight // 图片高度
if (iw / ih < ww / wh) { // 以高为主
this.ratio = ih / wh
this.canvasHeight = wh
this.canvasWidth = wh * iw / ih
} else { // 以宽为主
this.ratio = iw / ww
this.canvasWidth = ww
this.canvasHeight = ww * ih / iw
}
// 初始化画布大小
imgCanvas.width = this.canvasWidth
imgCanvas.height = this.canvasHeight
drawCanvas.width = this.canvasWidth
drawCanvas.height = this.canvasHeight
saveCanvas.width = this.canvasWidth
saveCanvas.height = this.canvasHeight
// 图片加载绘制
let img = document.createElement('img')
img.src = this.imgUrl
img.onload = () => {
console.log('图片已加载')
this.imgCtx.drawImage(img, 0, 0, this.canvasWidth, this.canvasHeight)
this.renderDatas() // 渲染原有数据
}
},
startDraw: function () { // 绘制区域
if (this.isDrawing) return
this.isDrawing = true
// 绘制逻辑
drawCanvas.addEventListener("click", this.drawImageClickFn.bind(this))
drawCanvas.addEventListener("dblclick", this.drawImageDblClickFn.bind(this))
drawCanvas.addEventListener("mousemove", this.drawImageMoveFn.bind(this))
},
clearAll: function () { // 清空所有绘制区域
console.log('清空所有绘制区域')
console.log(this.saveCtx, this.canvasWidth)
this.saveCtx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
this.drawedPoints = []
},
getImage: function () { // 请求图片
this.imgUrl = 'http://dx.pinhao.cn/gzh/upload/1705055943.jpg'
this.imgWidth = 500
this.imgHeight = 338
this.imgUrl && this.initImgCanvas()
return
this.$axios.post('/dsj/screenshot/', {
"source": "rtsp://admin:admin12345@10.10.42.251:554/cam/realmonitor?channel=1&subtype=0"
}).then(res => {
this.imgUrl = res.data.img
this.imgWidth = res.data.width
this.imgHeight = res.data.height
this.imgUrl && this.initImgCanvas()
})
},
drawImageClickFn: function (e) {
let drawCtx = this.drawCtx
if (e.offsetX || e.layerX) {
let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;
let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;
let lastPoint = this.drawingPoints[this.drawingPoints.length - 1] || []
if (lastPoint[0] !== pointX || lastPoint[1] !== pointY) {
this.drawingPoints.push([pointX, pointY])
}
}
},
drawImageMoveFn: function (e) {
let drawCtx = this.drawCtx
if (e.offsetX || e.layerX) {
let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;
let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;
// 绘制
drawCtx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
// 绘制点
drawCtx.fillStyle = 'blue'
this.drawingPoints.forEach((item, i) => {
drawCtx.beginPath();
drawCtx.arc(...item, 6, 0, 180)
drawCtx.fill(); //填充
})
// 绘制动态区域
drawCtx.save()
drawCtx.beginPath();
this.drawingPoints.forEach((item, i) => {
drawCtx.lineTo(...item)
})
drawCtx.lineTo(pointX, pointY)
drawCtx.lineWidth = "3";
drawCtx.strokeStyle = "blue";
drawCtx.fillStyle = 'rgba(255, 0, 0, 0.3)'
drawCtx.stroke();
drawCtx.fill(); //填充
drawCtx.restore()
}
},
drawImageDblClickFn: function (e) {
let drawCtx = this.drawCtx
let saveCtx = this.saveCtx
if (e.offsetX || e.layerX) {
let pointX = e.offsetX == undefined ? e.layerX : e.offsetX;
let pointY = e.offsetY == undefined ? e.layerY : e.offsetY;
let lastPoint = this.drawingPoints[this.drawingPoints.length - 1] || []
if (lastPoint[0] !== pointX || lastPoint[1] !== pointY) {
this.drawingPoints.push([pointX, pointY])
}
}
// 清空绘制图层
drawCtx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
// 绘制区域至保存图层
this.drawSaveArea(this.drawingPoints)
this.drawedPoints.push(this.drawingPoints)
this.drawingPoints = []
this.isDrawing = false
// 绘制结束逻辑
drawCanvas.removeEventListener("click", this.drawImageClickFn)
drawCanvas.removeEventListener("dblclick", this.drawImageDblClickFn)
drawCanvas.removeEventListener("mousemove", this.drawImageMoveFn)
},
drawSaveArea: function (points) {
if (!points instanceof Array || points.length === 0) return
this.saveCtx.save()
this.saveCtx.beginPath();
points.forEach((item, i) => {
this.saveCtx.lineTo(...item)
})
this.saveCtx.closePath()
this.saveCtx.lineWidth = "2";
this.saveCtx.fillStyle = 'rgba(255,0, 255, 0.3)'
this.saveCtx.strokeStyle = "red";
this.saveCtx.stroke();
this.saveCtx.fill(); //填充
this.saveCtx.restore()
},
savePoints: function () { // 将画布坐标数据转换成提交数据
let objectPoints = []
// "object": [{"polygon": {"x1":700,"y1":273,"x2":975,"y2":278,"x3":1107,"y3":368,"x4":718,"y4":354} }]
objectPoints = this.drawedPoints.map(area => {
let polygon = {}
area.forEach((point, i) => {
polygon[`x${i + 1}`] = Math.round(point[0] * this.ratio)
polygon[`y${i + 1}`] = Math.round(point[1] * this.ratio)
})
return {
"polygon": polygon
}
})
this.submitData = objectPoints
console.log('最终提交数据', objectPoints)
},
renderDatas: function () { // 将提交数据数据转换成画布坐标
this.drawedPoints = this.submitData.map(item => {
let polygon = item.polygon
let points = []
for (let i = 1; i < Object.keys(polygon).length / 2 + 1; i++) {
if (!isNaN(polygon[`x${i}`]) && !isNaN(polygon[`y${i}`])) {
points.push([polygon[`x${i}`] / this.ratio, polygon[`y${i}`] / this.ratio])
}
}
this.drawSaveArea(points)
return points
})
}
}
func.initCanvas()
func.getImage()
</script>
</html>