最近,做了一个使用canvas绘制矩形框,并生成对应的input进行输入,刚开始想偷懒,便找了很多类似的文章,可没有一个很完全满足需求的,只能自己搞了。记录一下。先看示例图,有点简陋啊,如果有需要的小伙伴可以拿去用
简单说下示例的功能:绘制一个矩形框,对应左上角生成一个input默认值填充,支持修改输入,右键矩形框可以删除对应矩形框
代码如下:
js
<template>
<div class="mycanvas-container">
<div ref="canvasRef" style="position: relative;"><canvas ref="markCanvas" width="640" height="480"></canvas></div>
<select name="mode" v-model="mode">
<option value="rect">绘制矩形框</option>
</select>
</div>
</template>
<script>
export default {
data() {
return {
rectArray: [
{ "x": 275.5375061035156, "y": 83, "w": 252, "h": 185, 'label_id': '测试' }
], // 矩形的数据存储
history: [],
dragging: false,
mode: 'rect',
mousedown: null,
ctx: null,
rectIndex: undefined // 当前点击的矩形的索引
}
},
mounted() {
// 禁用掉
document.oncontextmenu = function(e) {
e.preventDefault()
}
this.initCanvas(); // 画布初始化
},
beforeDestroy() {
},
methods: {
/* 画布初始化 */
initCanvas() {
let that = this
this.$nextTick(() => {
// 初始化canvas宽高
let cav = this.$refs.markCanvas;
cav.width = '800';
cav.height = '600';
that.ctx = cav.getContext('2d');
that.ctx.strokeStyle = 'red'
cav.style.cursor = 'crosshair'
cav.style.border="1px solid black"
that.addHistoy(cav)
// 数据回显绘制矩形
this.rectArray.length > 0 && this.rectArray.forEach(item => {
that.rectIndex = 0
that.ctx.beginPath();
that.ctx.lineWidth=2
that.ctx.rect(item.x, item.y, item.w, item.h);
that.ctx.stroke();
that.ctx.restore();
if (item.label_id) {
that.addInput(item.x, item.y, item.label_id)
}
that.addHistoy(cav, 'rect')
})
// 鼠标事件
cav.onmousedown = function(e) {
let sX = e.clientX
let sY = e.clientY
e.preventDefault();
that.mousedown = that.windowToCanvas(cav, e.clientX, e.clientY, e.which)
that.dragging = true
that.rectIndex = undefined
that.rectArray.forEach(function (value, index) {
if (value.w > 0 && value.h > 0 && sX > value.x && sX < value.x + value.w && sY > value.y && sY < value.y + value.h) {
// 鼠标在右下方向生成的矩形中
that.rectIndex = index
}
})
// 右键点击矩形框进行删除
if(e.button == 2 && that.rectIndex !== undefined) {
const selfChild = that.$refs.canvasRef.querySelectorAll('input')
that.rectArray.forEach((item, index) => {
that.$refs.canvasRef.removeChild(selfChild[index])
})
that.rectArray.splice(that.rectIndex, 1)
that.rectIndex = undefined
that.dragging = false
that.initCanvas()
return
}
}
cav.onmousemove = function(e) {
e.preventDefault();
if (that.dragging && that.mode === 'rect') { // 只有绘制矩形框时有效果
that.showLastHistory() // 每次绘制先清除上一次
that.updateRect(that.ctx, that.windowToCanvas(cav, e.clientX, e.clientY, e.which))
}
}
cav.onmouseup = function(e) {
e.preventDefault();
if(that.dragging) {
that.mode === 'rect' && that.drawRect(cav, that.windowToCanvas(cav, e.clientX, e.clientY, e.which))
}
that.dragging = false
}
// 阻止页面的右击菜单栏
cav.oncontextmenu = function(e) {
e.preventDefault()
}
})
},
// 坐标转化为canvas坐标
windowToCanvas(cav, x, y, type) {
//返回元素的大小以及位置
let bbox = cav.getBoundingClientRect();
// bbox 的宽度会加上 canvas 的 border 会影响精度
return {
x: x - bbox.left * (cav.width / bbox.width),
y: y - bbox.top * (cav.height / bbox.height),
type: type
}
},
updateRect(ctx, point) {
let w = Math.abs(point.x - this.mousedown.x)
let h = Math.abs(point.y - this.mousedown.y)
let x = point.x > this.mousedown.x ? this.mousedown.x : point.x
let y = point.y > this.mousedown.y ? this.mousedown.y : point.y
ctx.save();
ctx.beginPath();
ctx.lineWidth=2
ctx.rect(x, y, w, h);
ctx.stroke();
ctx.restore();
},
drawRect(cav, point) {
let w = Math.abs(point.x - this.mousedown.x)
let h = Math.abs(point.y - this.mousedown.y)
if(w > 0 && h > 0) {
let left = point.x > this.mousedown.x ? this.mousedown.x : point.x
let top = point.y > this.mousedown.y ? this.mousedown.y : point.y
this.rectArray.push({ x: left, y: top, w: w, h: h, label_id: '表' + (this.rectArray.length + 1), index: this.rectArray.length })
this.addHistoy(cav) // 保存上一次数据
this.addInput(this.mousedown.x, this.mousedown.y)
}
},
addInput(x, y, name) {
let container = this.$refs.canvasRef
let input = document.createElement('input');
input.type = 'text';
input.style.position = 'absolute';
input.style.left = x + 'px';
input.style.top = (y - 30) + 'px';
input.dataset.aa = this.rectArray.length
input.value = name ? name : '表' + this.rectArray.length
input.onkeyup = this.handleEnter;
container.appendChild(input);
input.focus()
},
handleEnter(e) {
this.rectArray[e.target.dataset.aa - 1]['label_id'] = e.target.value
this.$forceUpdate()
},
addHistoy(cav, mode) {
this.history.push({
mode: mode || this.mode,
data: this.ctx.getImageData(0, 0, cav.width, cav.height)
})
},
showLastHistory() {
this.ctx.putImageData(this.history[this.history.length - 1]['data'], 0, 0)
}
}
}
</script>
<style lang="less" scoped>
// 设置绘图样式1
body {
user-select: none;
}
.myMenu {
position: fixed;
top: 400px;
left: 400px;
width: 100px;
padding: 8px 0;
background-color: #fff;
> * {
width: 100%;
}
}
#canvas > div {
/* border: 2px solid green; */
position: absolute;
background-color: transparent;
}
#canvas > div > span {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-family: simsun;
font-size: 9pt;
}
// 设置绘图样式2
.mycanvas-container {
display: flex;
justify-content: center;
align-items: center;
.left,
.center,
.right {
width: 300px;
// height: 520px;
margin: 20px;
p {
text-align: center;
}
.myshow,
.myedit {
width: 300px;
height: 500px;
border: 1px solid #000;
position: relative;
.myedit-span {
position: absolute;
border: 1px dashed #fff;
background: white;
background-size: contain;
}
.mycanvas {
border: 1px solid pink;
position: absolute;
top: 0;
left: 0;
}
img {
width: 100%;
}
}
}
.right {
width: 150px;
display: flex;
justify-content: center;
align-items: left;
flex-direction: column;
.mybutton {
margin-top: 20px;
display: block;
}
}
}
</style>