前言:开发苦运营活动久矣,运营亦吐槽开发繁多;凡遇活动皆需从头开发,时间紧、任务急,耗资甚多,全员疲惫,苦不堪言!值此危急存亡之秋,吾挺身而出(我来!!!)。在后台搞一个图片热区框选的功能,你们运营自己去后台框框选选,配置好想跳转的功能,自己想怎么搞怎么搞去吧!

备注:此功能适用于活动不是很复杂,且没有复杂交互的活动页,即海报页。通常用于运营海报页活动引流至某些功能或者配置一些不固定文案的场景(提供个思路,如年终总结之类)

进入正文: 

提高看客效率,咱先贴图,不是你的菜,看完图就撤,就当您没来过!哈哈(网上随便找的海报示例图)

这是上传的原图:

这是进行热区框选之后的样子:

从上图可以发现,经过了鼠标框选,我们需要的热区就已经确定好了,剩下的就是双击热区进行对应事件的绑定配置了,这里就结合自身的业务具体来了。这里我们只讲核心——热区框选的交互 

基本思路就是:1.鼠标点击左键事件mousedown(开始);2.鼠标滑动事件onmousemove;3.根据鼠标滑动坐标计算热区的区域;4.鼠标抬起mouseup,绘制热区结束,得到最终的热区对象

 上代码说事吧

<template>
  <div>
    <div class="img_box"  @mouseup.left.stop="changeStop()">
      <div class="container" id="img-box-container">
        <img 
          ref="backgroundImg"
          :src="imgUrl" 
          ondragstart="return false;"
          oncontextmenu="return false;"
          onselect="document.selection.empty();"
          alt=""
          @mousedown.left.stop="mouseDown($event)"
        />
        <!--绘制的热区-->
        <div
          v-show="caseShow"
          class="area"
          :style="{
            width: areaWidth + 'px',
            height: areaHeight + 'px',
            left: starX + 'px',
            top: starY + 'px',
          }"
        />
        <HotAreaBox
          v-for="(item, index) in areaData"
          :index="index"
          :key="index"
          :areaInit="item"
          :parent-width="parentWidth"
          :parent-height="parentHeight"
          @updateArea="updateArea"
          @delAreaBox="delAreaBox"
        ></HotAreaBox>
      </div>
    </div>
  </div>
</template>
<script lang="ts">
import {Component, Emit, Prop, Vue, Watch} from 'vue-property-decorator';
import HotAreaBox from '../hotAreaBox/hotAreaBox.vue';
@Component({
  components: {
    HotAreaBox,
  },
})
export default class ImageHot extends Vue {
  @Prop({
    default: '',
  })
  imgUrl!: string;
  @Prop({
    default:[]
  })
  areaDataList!: any;

  starX = 0;
  // endX: 0;
  starY = 0;
  // endY = 0;
  areaWidth = 0;
  areaHeight = 0;
  parentWidth = 0;
  parentHeight = 0;
  caseShow = false;
  nowImgWidth = null;
  areaData: any =  [];


  mounted () {
    const parentDiv: any = document.querySelector("#img-box-container");
    //获取元素的宽高
    this.parentWidth = parentDiv.clientWidth;
    this.parentHeight = parentDiv.clientHeight;
    this.areaData = this.areaDataList
    console.log(this.areaData,"000000000000")
  }
  // 绘画热区开始
  mouseDown(e: any) {
    const backgroundImg: any = this.$refs.backgroundImg;
    // 显示热区
    this.caseShow = true;
    // 记录滑动的初始值
    // this.starX = e.offsetX;
    // this.starY = e.offsetY;
    this.starX = e.layerX;
      this.starY = e.layerY;
    // 鼠标滑动的过程
    if (!document.onmousemove) {
      document.onmousemove = (ev: any) => {
        if(ev.layerX<=backgroundImg.width&&ev.layerY<=backgroundImg.height){
          this.areaWidth = ev.layerX - this.starX;
          this.areaHeight = ev.layerY - this.starY;
        }else{
          this.changeStop()

        }
        
      };
    }
  }
  // 绘画热区结束
  changeStop() {
    document.onmousemove = null;
    if (this.caseShow && this.areaWidth > 10 && this.areaHeight > 10) {
      const backgroundImg: any = this.$refs.backgroundImg;
      const obj = {
        starX: this.starX,
        starY: this.starY,
        areaWidth: this.areaWidth,
        areaHeight: this.areaHeight,
        nowImgWidth: backgroundImg.width,
        nowImgHeight:backgroundImg.height,
      };
      this.areaData.push(obj);
    }
    // 初始化绘图
    this.caseShow = false;
    this.starX = 0;
    this.starY = 0;
    this.areaWidth = 0;
    this.areaHeight = 0;
  }
  // 删除指定热区
  delAreaBox(index: number) {
    console.log(index,"索引值")
    setTimeout(() => {
      this.areaData.splice(index, 1);
    }, 300);
    
  }
  // 更新热区
  updateArea(obj: any){
    this.areaData[obj.index] = obj
    console.log(this.areaData)
    this.updateAreaData()
  }
  // 通知父组件更新热区数据
  updateAreaData(){
    this.$emit('updateAreaDataList', this.areaData);
  }
}
</script>
<style lang="less" scoped>
  .img_box{
    max-height: 1000px;
    overflow-y: auto;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    .container{
      
      position: relative;
      img{
        width: 750px;
        cursor: crosshair;
      }
      .area{
        position: absolute;
        width: 200px;
        height: 200px;
        left: 200px;
        top: 300px;
        background: rgba(#2980b9, 0.3);
        border: 1px dashed #34495e;
      }
    }
  }
</style>

热区区域组件的代码:

<template>
  <div>
    <div
      class="areaBox"
      :style="{width: areaInit.areaWidth+'px',height: areaInit.areaHeight+'px',left: areaInit.starX+'px',top: areaInit.starY+'px'}"
      @dblclick="doubleClickSet"
      @mousedown.left.stop="mouseDown($event)"
      @mouseup.left.stop="mouseUp($event)"
    >
      <div class="area_name" :title="'热区名称:'+areaInit.hotAreaName">热区名称:{{areaInit.hotAreaName}}</div>
      <span class="promptText">双击{{areaInit.pathMsg?'编辑':'设置'}}热区</span>
      <div class="area_path" v-if="areaInit.pathMsg" :title="'跳转:'+areaInit.pathMsg.pathName">跳转:{{areaInit.pathMsg.pathName}}</div>
      <!--删除-->
      <div class="del" @click.stop="del()">X</div>
      <!--形变点-->
      <div class="shape" @mousedown.left.stop="shapeDown($event)" @mouseup.left.stop="mouseUp($event)"/>
    </div>

  </div>
  

  
</template>

<script lang="ts">
import {Component, Emit, Prop, Vue, Watch} from 'vue-property-decorator';
import { namespace } from 'vuex-class';
const { State, Action, Mutation } = namespace('activityConfig/activityManage');
@Component({
  components: {
    
  },
})
export default class AreaBox extends Vue {
  
  @Prop({
    default: {},
  })
  areaInit: any;
  @Prop({
    default:0
  })
  parentWidth!: number
  @Prop({
    default:0
  })
  parentHeight!: number
  @Prop({
    default:null,
  })
  index!: number

  starX = 0;
  starY = 0;
  star1X = 0;
  star1Y = 0;
  // box操作初始点
  move = {
    // 拖动
    startX: 0,
    starY: 0,
    // 形变
    start1X: 0,
    start1Y: 0
  };
  dirty = false;
  hotAreaModal = false;
  
  

  get form1() {
    return this.$form.createForm(this, {
      onFieldsChange: () => {
        this.dirty = true;
      },
    });
  }
  mounted () {
    
  }
  // 开始拖动
  mouseDown(e: any) {
    console.log(e)
    this.starX = e.clientX;
    this.starY = e.clientY;
    if (!document.onmousemove) {
      const initX = this.areaInit.starX
      const initY = this.areaInit.starY
      document.onmousemove = (ev: any) => {
        // console.log(ev)
        const position = {
          nowX: initX + ev.clientX - this.starX,
          nowY:initY + ev.clientY - this.starY
        }
        if(position.nowX<0){//说明热区超出图片左侧区域了
          return false
        }
        if(position.nowY<0){//说明热区超出图片顶部区域了
          return false
        }
        const nowRight = position.nowX + this.areaInit.areaWidth;
        
        const nowBottom = position.nowY + this.areaInit.areaHeight;
        console.log(nowBottom,this.areaInit.nowImgHeight,"0000")
        if(nowRight>=this.areaInit.nowImgWidth){
          position.nowX = this.areaInit.nowImgWidth - this.areaInit.areaWidth-10
        }
        if(nowBottom>=this.areaInit.nowImgHeight){
          position.nowY = this.areaInit.nowImgHeight - this.areaInit.areaHeight
        }
        this.areaInit.starX = position.nowX
        this.areaInit.starY = position.nowY
      }
    }
  }
  // 结束拖动/变形
  mouseUp() {
    if(document.onmousemove){
      document.onmousemove = null
      this.areaInit.index = this.index
      this.$emit('updateArea', this.areaInit);
    }
  }
  // 形变开始
  shapeDown(e: any) {
    this.star1X = e.clientX
    this.star1Y = e.clientY
    if (!document.onmousemove) {
      const initX = this.areaInit.areaWidth
      const initY = this.areaInit.areaHeight
      document.onmousemove = (ev: any) => {
         if(ev.layerX<=this.areaInit.nowImgWidth&&ev.layerY<=this.areaInit.nowImgHeight){
         this.areaInit.areaWidth = initX + ev.clientX - this.star1X-10
          this.areaInit.areaHeight = initY + ev.clientY - this.star1Y
        }else{
          this.mouseUp()

        }
        // this.areaInit.areaWidth = initX + ev.clientX - this.star1X
        // this.areaInit.areaHeight = initY + ev.clientY - this.star1Y
      }
    }
  }
  // 删除
  del() {
    this.$emit('delAreaBox', this.index)
  }
  // 双击进行热区路径设置
  doubleClickSet(){
    document.onmousemove = null
    
    
  }
  
  // 添加/编辑热区跳转路径
  confirmHotAreaPath(){
    if(!this.hotAreaName){
      return this.$message.warning('请输入热区名称')
    }
    
    this.addHotAreaPath()
  }
  addHotAreaPath(){
    this.hotAreaModal = false
    switch (this.funType) {
      case 0://医生列表
        this.areaInit.hotAreaName = this.hotAreaName,
        this.areaInit.pathMsg = {
          pathType:this.funType,
          pathName:this.pathTypeText(this.funType)
        }
        this.$emit('updateArea', this.areaInit);
        break;
      case 1://医生主页
        this.areaInit.hotAreaName = this.hotAreaName,
        this.areaInit.pathMsg = {
          pathType:this.funType,
          pathName:this.pathTypeText(this.funType)+'-'+this.doctorData.name,
          defaultValue:this.doctorDefault,
          valueData:this.doctorData
        }
        this.$emit('updateArea', this.areaInit);
        break;
      default:
        break;
    }
  }
}
</script>

<style lang="less" scoped>
  .areaBox {
    position: absolute;
    background: rgba(#2980b9, 0.3);
    border: 0.7px dashed #34495e;
    display: flex;
    justify-content: center;
    align-items: center;
    color: #34495e;
    font-size: 14px;
    cursor: move;

    .promptText {
      overflow: hidden;
      display: inline;
      max-width: 100%;
      max-height: 100%;
      text-align: center;
    }
    .area_name{
      width:100%;
      position: absolute;
      top: 0;
      left: 0px;
      font-size: 12px;
      color: #666666;
      padding-left: 10px;
      overflow: hidden;
      white-space: nowrap;
      text-overflow: ellipsis;
    }
    .area_path{
      width:100%;
      position: absolute;
      bottom: 0;
      left: 0px;
      font-size: 12px;
      color: #666666;
      padding-left: 10px;
      overflow: hidden;
      white-space: nowrap;
      text-overflow: ellipsis;
    }

    .del {
      width: 20px;
      height: 20px;
      background: #bdc3c7;
      border-radius: 50%;
      position: absolute;
      right: 0;
      top: 0;
      transform: translate3d(50%, -50%, 0);
      cursor: pointer;
      text-align: center;
      line-height: 20px;
      color: red;
    }

    .del:hover {
      background: #ecf0f1;
    }

    .shape {
      position: absolute;
      width: 7px;
      height: 7px;
      background: transparent;
      right: 0;
      bottom: 0;
      transform: translate3d(50%, 50%, 0);
      cursor: nwse-resize;
    }
  }
</style>

附言:方法流程代码已完整提供,仅供参考,也不可能复制了直接就能跑通的,总归是需要结合自身情况替换对应的某些参数来的。

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐