canvas实现图片框选-js版
由于项目中需要进行图片部分区域框选,用来做图片标注工作,之前也找过多种方案,比如徒手用div来实现,后来绕了一圈之后,还是发现canvas非常便捷,于是乎将项目中的核心代码单独提取出来,供各位学习。
核心代码如下,提供的是html + js的代码,下方的代码需要进行变动然后集成到vue中,注意部分坑即可。
<!DOCTYPE html>
<html>
<head>
<title>测试canvas</title>
<script src="https://cdn.bootcss.com/vue/2.4.2/vue.min.js"></script>
</head>
<body>
<div style="background:url('bd_logo1.png');width:540px;height:300px;">
<canvas id="myCanvas" width="540" height="300"></canvas>
</div>
</body>
<script type="text/javascript">
function draw(x, y, wid, hei){
var c=document.getElementById("myCanvas");
var cxt=c.getContext("2d");
var img = new Image();
cxt.drawImage(img, 0, 0, 340, 100);
cxt.fillStyle = "rgba(255, 0, 0, 0.1)";
cxt.fillRect(x, y, wid, hei);
cxt.strokeStyle = "rgb(0, 0, 0)"; //"rgb(0, 0, 0)";
cxt.strokeRect(x, y, wid, hei);
}
draw(1, 2, 100, 200);
draw(50, 50, 150, 350);
</script>
</html>
canvas实现图片框选-vue版
<template>
<div class="divbody">
<div class="imgContainer" ref="imgContainer">
<canvas
:ref="'refmyCanvas'"
class="canvasClass"
:width="divWidth"
:height="divHeight"
@mousedown="canvasMouseDown"
@mouseup="canvasMouseUp"
@mousemove="canvasMouseMove"
></canvas>
<img
:id="'image'"
:src="imageSrc"
:ref="'refimage'"
class="imgClass"
@load="uploadImgLoad"
v-show="false"
/>
</div>
</div>
</template>
<script>
export default {
data() {
return {
divWidth: 0,
divHeight: 0,
imageSrc: 'http://img2.imgtn.bdimg.com/it/u=3984473917,238095211&fm=26&gp=0.jpg',
// canvas的配置部分
c: '',
cxt: '',
canvasImg: '',
imgWidth: 0, // img框的宽度
imgHeight: 0, // img框的高度
targetMarkIndex: -1, // 目标标注index
params: {
currentX: 0,
currentY: 0,
flag: false, // 用来判断在canvas上是否有鼠标down的事件,
editFlag: false,
editIndex: -1
},
// 目标类别list
imageCategoryList: [],
targetMarkArray: []
}
},
mounted() {
// 这里是进行初始化canvas的操作 myCanvas
const self = this;
try {
self.c = self.$refs.refmyCanvas;
self.canvasImg = self.$refs.refimage;
self.cxt = self.c.getContext('2d');
self.divWidth = self.$refs.imgContainer.offsetWidth;
self.divHeight = self.$refs.imgContainer.offsetHeight;
} catch (err) {
console.log(err);
}
try {
self.canvasOnDraw(self.imgWidth, self.imgHeight);
} catch (err) {
console.log(err);
}
},
methods: {
// 鼠标down事件
canvasMouseDown(e) {
this.params.flag = true;
if (!e) {
e = window.event;
// 防止IE文字选中
this.$refs.refmyCanvas.onselectstart = function() {
return false;
};
}
// 这里先判断一下,看是否是在有效数据,并且初始化参数
if ((this.params.flag === true) && (this.params.editFlag === false)) {
this.params.currentX = 0;
this.params.currentY = 0;
this.params.currentX = e.layerX;
this.params.currentY = e.layerY;
const dict1 = {
x1: this.params.currentX, // 开始的x坐标
y1: this.params.currentY, // 开始的y坐标
x2: this.params.currentX, // 结束的x坐标
y2: this.params.currentY, // 结束的y坐标
flag: false, // 图片区域是否高亮,
targetMarkValue: '', // 目标类别值
wid: 0, // 矩形宽度
hei: 0, // 矩形高度
// final 是最终的框选的坐标值
left: this.params.currentX,
top: this.params.currentY,
width: 0, // 矩形宽度
height: 0, // 矩形高度
finalX1: this.params.currentX,
finalY1: this.params.currentY,
finalX2: this.params.currentX,
finalY2: this.params.currentY
};
this.targetMarkIndex = this.targetMarkIndex + 1;
this.targetMarkArray.push(dict1);
}
// 执行渲染操作
try {
this.canvasOnDraw(this.imgWidth, this.imgHeight);
} catch (err) {
console.log(err);
}
},
canvasMouseUp(e) {
this.params.flag = false;
try {
this.canvasOnDraw(this.imgWidth, this.imgHeight);
} catch (err) {
console.log(err);
}
},
canvasMouseMove(e) {
if (e === null) {
e = window.event;
}
if ((this.params.flag === true) && (this.params.editFlag === false)) {
this.params.currentX = e.layerX;
this.params.currentY = e.layerY;
this.targetMarkArray[this.targetMarkIndex].x2 = this.params.currentX; // x1 值
this.targetMarkArray[this.targetMarkIndex].y2 = this.params.currentY; // y1 值
this.targetMarkArray[this.targetMarkIndex].wid = this.params.currentX - this.targetMarkArray[this.targetMarkIndex].x1; // 宽度值
this.targetMarkArray[this.targetMarkIndex].hei = this.params.currentY - this.targetMarkArray[this.targetMarkIndex].y1; // 高度
}
// 执行渲染操作
try {
this.canvasOnDraw(this.imgWidth, this.imgHeight);
} catch (err) {
console.log(err);
}
},
uploadImgLoad(e) {
try {
this.imgWidth = e.path[0].naturalWidth;
this.imgHeight = e.path[0].naturalHeight;
this.canvasOnDraw(this.imgWidth, this.imgHeight);
} catch (err) {
console.log(err);
}
},
// 输入两个坐标值,判断哪个坐标值离左上角最近,其中特殊情况需要进行坐标查找工作
findWhichIsFirstPoint(x1, y1, x2, y2) {
// 首先判断x轴的距离谁更近
if (x1 <= x2) {
// 说明x1 比较小,接下来判断y谁更近
if (y1 <= y2) {
// 说明第一个坐标离得更近,直接顺序return就好
return [x1, y1, x2, y2];
} else {
// 这里遇见一个奇葩问题,需要进行顶角变换
return [x1, y2, x2, y1];
}
} else {
// 这里是x1 大于 x2 的情况
if (y2 <= y1) {
return [x2, y2, x1, y1];
} else {
// y2 大于 y1 的情况, 这里需要做顶角变换工作
return [x2, y1, x1, y2];
}
}
},
// canvas绘图部分
canvasOnDraw(imgW = this.imgWidth, imgH = this.imgHeight) {
const imgWidth = imgW;
const imgHeight = imgH;
this.divWidth = this.$refs.imgContainer.offsetWidth;
this.divHeight = this.$refs.imgContainer.offsetHeight;
this.cxt.clearRect(0, 0, this.c.width, this.c.height);
// 当前的图片和现有的canvas容器之前的一个关系,是否有必要,我们后续做讨论
var resPointList = this.changeOldPointToNewPoint(
imgWidth,
imgHeight,
this.divWidth,
this.divHeight
);
this.cxt.drawImage(
this.canvasImg,
0,
0,
imgWidth,
imgHeight,
0,
0,
resPointList[0],
resPointList[1]
);
for (const index in this.targetMarkArray) {
const x1 = this.targetMarkArray[index].x1;
const y1 = this.targetMarkArray[index].y1;
const x2 = this.targetMarkArray[index].x2;
const y2 = this.targetMarkArray[index].y2;
const wid = this.targetMarkArray[index].wid;
const hei = this.targetMarkArray[index].hei;
const FinalPointList = this.findWhichIsFirstPoint(
(x1 * this.imgWidth) / resPointList[0],
(y1 * this.imgHeight) / resPointList[1],
(x2 * this.imgWidth) / resPointList[0],
(y2 * this.imgHeight) / resPointList[1]
);
this.targetMarkArray[index].finalX1 = FinalPointList[0];
this.targetMarkArray[index].finalY1 = FinalPointList[1];
this.targetMarkArray[index].finalX2 = FinalPointList[2];
this.targetMarkArray[index].finalY2 = FinalPointList[3];
// 必须要有的字段
this.targetMarkArray[index].left = this.targetMarkArray[index].finalX1;
this.targetMarkArray[index].top = this.targetMarkArray[index].finalY1;
this.targetMarkArray[index].width = this.targetMarkArray[index].finalX2 - this.targetMarkArray[index].finalX1;
this.targetMarkArray[index].height = this.targetMarkArray[index].finalY2 - this.targetMarkArray[index].finalY1;
// 调整四个顶角的函数,为了能让整体框选区域更好看
const FinalPointListNow = this.findWhichIsFirstPoint(
x1,
y1,
x2,
y2
);
const tmpX1 = FinalPointListNow[0];
const tmpY1 = FinalPointListNow[1];
const tmpX2 = FinalPointListNow[2];
const tmpY2 = FinalPointListNow[3];
this.cxt.strokeStyle = '#FF6600';
this.cxt.strokeRect(tmpX1, tmpY1, tmpX2 - tmpX1, tmpY2 - tmpY1);
this.cxt.fillStyle = '#FF6600';
this.cxt.font = '16px Arial';
// canvas的标题部分
this.cxt.fillText(parseInt(index) + 1, tmpX1, parseInt(tmpY1) - 6);
// 这里是在处理高亮的地方
this.cxt.fillStyle = 'rgba(255, 0, 0, 0.1)';
this.cxt.fillRect(tmpX1, tmpY1, wid, hei);
// 说明被点击了
this.canvasDrowBorder(
'#FF6600',
tmpX1,
tmpY1,
tmpX2 - tmpX1,
tmpY2 - tmpY1
);
this.canvasDrowInnerColor(
'rgba(255, 0, 0, 0.3)',
tmpX1,
tmpY1,
tmpX2 - tmpX1,
tmpY2 - tmpY1
);
}
},
// canvas框选区域的内容颜色
canvasDrowInnerColor(color, x, y, w, h) {
this.cxt.fillStyle = color;
this.cxt.fillRect(x, y, w, h);
},
// canvas框选区域的边框颜色
canvasDrowBorder(color, x, y, w, h) {
this.cxt.strokeStyle = color;
this.cxt.strokeRect(x, y, w, h);
},
// 尺寸变换函数
changeOldPointToNewPoint(imgw, imgH, canvasW, canvasH) {
// 这里有个要求,先以宽度为准,然后再一步步调整高度
var tmpW = canvasW;
var tmpH = (tmpW * imgH) / imgw;
// 如果转换之后的高度正好小于框的高度,则直接进行显示
if (tmpH <= canvasH) {
// 尺寸完美匹配
return [tmpW, tmpH];
} else {
// 高度超出框了,需要重新调整高度部分
tmpW = canvasW;
tmpH = (tmpW * imgH) / imgw;
var count = 1;
var raise = 0.05;
while (tmpH > canvasH || tmpW > canvasW) {
tmpW = tmpW * (1 - raise * count);
tmpH = (tmpW * imgH) / imgw;
}
return [tmpW, tmpH];
}
}
}
}
</script>
<style lang="less" scoped>
.divbody {
min-width: 640px;
min-height: 480px;
}
.imgContainer {
position: relative;
width: 640px;
height: 480px;
}
.canvasClass {
position: absolute;
width: auto;
height: auto;
max-width: 100%;
max-height: 100%;
}
.imgClass {
width: auto;
height: auto;
max-width: 100%;
max-height: 100%;
}
</style>