canvas绘制多个矩形实现热区图功能

热区图功能:

  1. 在运用后台上传一张背景图,在背景图上框选指定区域,配置对应的跳转链接或领券信息
  2. 小程序端判断用户点击位置是否在矩形框选范围内,如果在指定范围内,根据后台配置的功能进行页面跳转或领券。
    在这里插入图片描述
    运营后台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
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值