js拖拽div放置目标元素,弹出弹窗(兼容移动端)

483 阅读1分钟

效果如下: 20210727-164806.gif

本文章写的是vue项目使用js原生方法实现拖拽,若使用html5新特性draggable无法兼容移动端。

思路:

第一步:监听鼠标按下的事件mousedown(移动端对应touchstart,后续不再附上移动端),用js原生方法cloneNode克隆dom,加上克隆块class方便我们控制克隆块的样式,再获取点击的节点相对它父元素的坐标 给克隆块绝对定位 使它们重叠,最后用js原生方法insertBefore将写好样式的克隆块插入到当前点击节点前。

第二步:监听鼠标移动的事件mousemove,给克隆块动态设置坐标,并计算触碰区域,判断是否和目标元素重叠(即自定义的computedPosition函数),如果重叠了,添加hover样式。

第三步:监听鼠标弹起的事件mouseUp,移除克隆块节点,移动弹起点落在目标元素上,则弹出弹窗(即做自己要做的操作)。

坑:

1、移动端事件默认参数与pc不同,这边采用的是e.changedTouches[0],可以使用vConsole移动端调试工具把参数打印出来看下。
2、如果应用到实际场景,可能会使移动端无法滑动页面,需要将阻止移动端事件默认事件去除(.stop.prevent)

代码:

html:
<template>
  <div class="draggable-container"
       @mousemove.stop.prevent='e => chooselistMoveFn(e)'
       @touchmove.stop.prevent='e => chooselistMoveFn(e)'
       @mouseup.stop.prevent='e => chooselistUpFn(e)'
       @touchend.stop.prevent='e => chooselistUpFn(e)'>
    <div class="draggable-element-container">
      <div
        :id="'start-item' + index"
        :class="['element-item', 'start-item']"
        v-for="(item, index) in [1,2,3,4]"
        :key="index"
        @mousedown.stop.prevent="e => chooselistDownFn(e, item)"
        @touchstart.stop.prevent="e => chooselistDownFn(e, item)"
      >
        拖我
      </div>
    </div>
    <div class="target-element-container">
      <div
        :id="'end-item' + index"
        :class="['element-item', 'end-item', activeHoverElementId == 'end-item' + index ? 'active-hover' : '']"
        v-for="(item, index) in [1,2,3]"
        :key="index"
      >
        放这
      </div>
    </div>
  </div>
</template>
js:
<script>
export default {
  name: 'draggable',
  data() {
    return {
      activeHoverElementId: ''
    };
  },
  methods: {
    chooselistDownFn(e, item) {
      e = e.type === 'touchstart' ? e.changedTouches[0] : e
      // 当前点击的节点
      let itm = e.target
      // 克隆的节点
      let cln = itm.cloneNode(true)
      /* 获取点击的节点相对它父元素的坐标 给克隆块绝对定位 使它们重叠 */
      cln.classList.add('clone-element', 'posiA')
      cln.style.top = e.target.offsetTop + 'px'
      cln.style.left = e.target.offsetLeft + 'px'
      e.target.parentNode.insertBefore(cln, itm)
    },
    chooselistMoveFn(e) {
      e = e.type === 'touchmove' ? e.changedTouches[0] : e
      let cln = document.querySelector('.clone-element')
      // 如果有克隆块存在
      if(cln) {
        // 动态定位克隆的元素
        cln.style.position = 'fixed'
        cln.style.top = e.clientY - 35 + 'px'
        cln.style.left = e.clientX - 30 + 'px'
        this.computedPosition(e, 'move')
      }
    },
    chooselistUpFn(e) {
      e = e.type === 'touchend' ? e.changedTouches[0] : e
      // 鼠标松开 克隆块初始化
      let cln = document.querySelector('.clone-element')
      if(cln) {
        cln.remove()
      }
      this.activeHoverElementId = ''
      this.computedPosition(e, 'up')
    },
    computedPosition(e, type) {
      // 获取所有目标元素的位置,并计算触碰区域坐标
      let targetELes = document.querySelectorAll('.end-item')
      for(let i = 0; i < targetELes.length; i++) {
        let { x, y, width, height} = targetELes[i].getBoundingClientRect()
        if(Math.abs(e.clientX - x - width / 2) < width && Math.abs(e.clientY - y - height / 2) < height) {
          // 进入区域了
          console.log('进入区域了');
          if(type === 'move') {
            this.activeHoverElementId = 'end-item' + i
          }
          if(type === 'up') {
            console.log('弹出弹窗')
            alert('弹窗内容')
          }
          return
        } else {
          this.activeHoverElementId = ''
        }
      }
    }
  }
};
</script>
css:
<style lang="scss" scoped>
.draggable-container {
width: 500px;
position: relative;
display: flex;
justify-content: space-around;
.draggable-element-container {
  .element-item {
    width: 50px;
    height: 50px;
    line-height: 50px;
    text-align: center;
    border-radius: 50%;
    background: lightgreen;
    margin-top: 10px;
    cursor: move;
    &:hover {
      opacity: 0.8;
    }
  }
  .clone-element {
    width: 60px;
    height: 60px;
    line-height: 60px;
    text-align: center;
    border-radius: 50%;
    background: lightgreen;
    position: absolute;
    // top: 0;
    // left: 0;
    z-index: 2; //为了在目标元素上方
    // transition: all 0.2s linear;
  }
}
.target-element-container {
  .element-item {
    width: 40px;
    height: 40px;
    line-height: 40px;
    text-align: center;
    border-radius: 50%;
    background: lightblue;
    margin-top: 20px;
    transition: transform 0.2s linear;
  }
  .active-hover {
    transform: scale(1.5);
  }
}
}
</style>