热区图功能:
- 在运用后台上传一张背景图,在背景图上框选指定区域,配置对应的跳转链接或领券信息
- 小程序端判断用户点击位置是否在矩形框选范围内,如果在指定范围内,根据后台配置的功能进行页面跳转或领券。
运营后台canvas绘制矩形组件
<template>
<div class="img-intercept-content" :style="hotStyle">
<div class="imgContainer" :style="hotStyle" ref="imgContainer">
<canvas
ref="refInterceptCanvas"
class="canvasClass"
:width="divWidth"
:height="divHeight"
@mousedown="canvasMouseDown"
@mouseup="canvasMouseUp"
@mousemove="canvasMouseMove"
@mouseleave="canvasMouseLeave"
></canvas>
<img
:id="'image'"
:src="imageSrc"
ref="refInterceptImage"
class="imgClass"
@load="uploadImgLoad"
v-show="false"
/>
</div>
</div>
</template>
<script>
export default {
name: 'imageIntercept',
data() {
return {
divWidth: 0,
divHeight: 0,
// canvas的配置部分
canvasObj: '',
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: [],
allPosition: [],
hotStyle: {
height: (window.innerHeight - 130 > 300 ? window.innerHeight - 130 : 400) + 'px',
overflow: 'auto'
},
deleteSubIndex: -1,
editSubIndex: -1
}
},
props: {
imageSrc: {
type: String,
default: ''
},
positionList: {
type: Array,
default: ()=>{
return []
}
},
change: {
type: Function,
default: ()=>{
}
},
editHotNumber: {
type: Number,
default: -1
}
},
computed: {
},
watch: {
},
mounted() {
this.initCanvas()
},
methods: {
// 初始化canvas
initCanvas() {
try {
this.canvasObj = this.$refs.refInterceptCanvas;
this.canvasImg = this.$refs.refInterceptImage;
this.cxt = this.canvasObj.getContext('2d');
this.divWidth = this.$refs.imgContainer.offsetWidth;
this.divHeight = this.$refs.imgContainer.offsetHeight;
} catch (err) {
console.log(err);
}
try {
this.canvasOnDraw(this.imgWidth, this.imgHeight);
} catch (err) {
console.log(err);
}
},
// 鼠标down事件
canvasMouseDown(e) {
if (this.editHotNumber === -1){
return
}
this.params.flag = true;
if (!e) {
e = window.event;
// 防止IE文字选中
this.$refs.refInterceptCanvas.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;
if (this.editHotNumber !== -1) {
dict1.hotNumber = this.editHotNumber
}
this.targetMarkArray.push(dict1);
}
// 执行渲染操作
try {
this.canvasOnDraw(this.imgWidth, this.imgHeight);
} catch (err) {
console.log(err);
}
},
canvasMouseUp(e) {
if (this.editHotNumber === -1){
return
}
this.params.flag = false;
try {
// 数据去重
let oldDataLength = this.targetMarkArray.length
this.targetMarkArray = this.uniqueArrByObj(this.targetMarkArray, 'hotNumber')
if (oldDataLength !== this.targetMarkArray.length) {
this.refreshCanvas()
return
}
let isOriginalPoint = false
this.targetMarkArray = this.targetMarkArray.filter((mark) => {
// 如果x1>x2,调换x1和x2的值
if (mark.x1 >= mark.x2) {
let x1 = mark.x1
mark.x1 = mark.x2
mark.x2 = x1
}
// 如果y1>y2,调换y1和y2的值
if (mark.y1 >= mark.y2) {
let y1 = mark.y1
mark.y1 = mark.y2
mark.y2 = y1
}
if (mark.x2 - mark.x1 < 5 || mark.y2 - mark.y1 < 5) {
isOriginalPoint = true
return false
} else {
return true
}
})
// 判断是否原点,宽高均为0或宽高小于5, 重绘
if (isOriginalPoint) {
this.refreshCanvas()
return
}
// 矩形重叠判断
let changeData = {
tempNumber: this.deleteSubIndex !== -1 ? this.deleteSubIndex : (this.editSubIndex !== -1 ? this.editSubIndex : this.targetMarkIndex),
position: {
}
}
this.targetMarkArray.filter(row=>{
if (this.editHotNumber === row.hotNumber) {
changeData.position = row
}
})
// console.log('当前坐标:' + JSON.stringify(changeData.position))
let repeatList = this.judgePositionRepeat(this.targetMarkArray)
if ((!repeatList || !repeatList.length) && Object.keys(changeData.position).length) {
this.$emit('change', changeData)
} else if (repeatList && repeatList.length) {
let repeatIndexMap = {
}
repeatList.filter(row => {
let repeatIndex = null
Array.isArray(row) && row.filter((item) => {
this.targetMarkArray.filter((mark, markIndex) => {
if (item.x1 === mark.x1 && item.x2 === mark.x2 && item.y1 === mark.y1 && item.y2 === mark.y2) {
repeatIndex = repeatIndex == null ? markIndex : (repeatIndex > markIndex ? repeatIndex : markIndex)
}
})
})
if (repeatIndex != null) {
repeatIndexMap[repeatIndex] = repeatIndex
}
})
this.targetMarkArray = this.targetMarkArray.filter((row, index) => {
if (index === repeatIndexMap[index]) {
return false
} else {
return true
}
})
this.refreshCanvas()
this.$message.error('选择的区域不能重叠')
}
} catch (err) {
console.log(err);
}
},
canvasMouseMove(e) {
if (this.editHotNumber === -1){
return
}
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);
}
},
canvasMouseLeave(e){
this.canvasMouseUp(e)
},
uploadImgLoad(e) {
try {
this.imgWidth = e.path[0].naturalWidth;
this.imgHeight = e.path[0].naturalHeight;
let timeout = setTimeout(()=>{
this.positionListChange()
this.canvasOnDraw(this.imgWidth, this.imgHeight)
clearTimeout(timeout)
}, 200)
this.canvasOnDraw(this.imgWidth, this.imgHeight);
} catch (err) {
console.log(err);
}
},
positionListChange () {
let targetMarkArray = []
if (Array.isArray(this.positionList)) {
this.positionList.filter(row => {
if (row.position && Object.keys(row.position).length) {
if (row.hotNumber || row.hotNumber === 0) {
row.position.hotNumber = row.hotNumber
}
targetMarkArray.push(row.position)
}
})
this.targetMarkArray = targetMarkArray
this.targetMarkIndex = this.targetMarkArray.length - 1
}
},
// 输入两个坐标值,判断哪个坐标值离左上角最近,其中特殊情况需要进行坐标查找工作
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) {
if (!imgW) {
return
}
const imgWidth = imgW;
const imgHeight = imgH;
this.divWidth = imgW;
this.divHeight = imgH;
this.cxt.clearRect(0, 0, this.canvasObj.width, this.canvasObj.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) {
let markItem = this.targetMarkArray[index]
const x1 = markItem.x1;
const y1 = markItem.y1;
const x2 = markItem.x2;
const y2 = markItem.y2;
const wid = markItem.wid;
const hei = markItem.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]
);
markItem.finalX1 = FinalPointList[0];
markItem.finalY1 = FinalPointList[1];
markItem.finalX2 = FinalPointList[2];
markItem.finalY2 = FinalPointList[3];
// 必须要有的字段
markItem.left = markItem.finalX1;
markItem.top = markItem.finalY1;
markItem.width = markItem.finalX2 - markItem.finalX1;
markItem.height = markItem.finalY2 - markItem.finalY1;
// 调整四个顶角的函数,为了能让整体框选区域更好看
const FinalPointListNow = this.findWhichIsFirstPoint