[TOC]

效果图

话不多说先上图看效果
2020-11-05 15.32.26.gif

初级

为了方便理解selectDom可以直接获取元素的DOM

 selectDom (id) {
      return document.getElementById(id)
 },

按下

给需要拖动的元素添加事件
细节处理防止该元素冒泡:prevent就相当于…event.preventDefault()

@mousedown.prevent="onMousedown($event,item)"
  • dragItem是当前选中项目,可在data中挂上这个变量,初始值为null
  • mouseOffsetX,mouseOffsetY是记录的推动的初始值,按下那一个刻,没有拖动,所以初始值为零
  • 要设置状态来标记元素是否可拖动-isDrag
  • tagName是拖动元素的名字,根据需要即为拖动元素需要携带的业务数据
  • dragDiv是拖动的元素,borderDiv是可拖动区域
  • 该元素在拖动区域内饰绝对定位,mouseOffsetX的相对偏移量是鼠标当前距离左边窗口的距离- borderDiv的距离左侧的距离 = 元素距离边框左侧的距离,mouseOffsetY同理
  • 按下的开始,就要开始监听该元素的移动和抬起事件
      onMousedown(e,key) {
        this.dragItem.mouseOffsetX = 0 //偏移量
        this.dragItem.mouseOffsetY = 0 //偏移量
        this.dragItem.isDrag = true //标记是否可拖拽
        this.dragItem.tagName =  key
        let dragDiv = this.selectDom('drag' + key)
        let borderDiv = this.selectDom('ai-drawer')
        this.dragItem.mouseOffsetX = e.pageX - borderDiv.offsetLeft
        this.dragItem.mouseOffsetY = e.pageY - borderDiv.offsetTop
         document.addEventListener('mousemove', this.onMouseMove, true)
        document.addEventListener('mouseup', this.onMouseUp, true)
      },
    

    移动

    拖动事件中有很多坐标信息
    如:可以私下去了解下,多数情况下都是一致的, pageY,clientY,offsetTop
  1. 首先获取元素的dom和可拖动区域的DOM
  2. 记录拖动时鼠标的坐标,并设置moveX,moveY的初始值
  3. 在变更元素位置之前判断dragItem当前是否可拖拽
  4. 元素的横坐标 = 鼠标坐标 - 图片左侧位置 - 圆的半径
  5. 接下来需要范围限定,moveX,moveY不能小于可拖动区域offsetWidth,offsetHeight,所以他们最小为0,最大borderDiv.offsetWidth -20(元素是个半径为20px的⭕️)
  6. 拖动位置确定后,记录在dragItem对象上

     onMouseMove(e) {
       let borderDiv = this.selectDom('ai-drawer')
       let dragDiv = this.selectDom('drag' + this.dragItem.tagName)
       if (!dragDiv) return // 处理加载时候异常获取style
       //鼠标位置
       let mouseX = e.pageX
       let mouseY = e.pageY
       // 元素的新位置
       let moveX = 0
       let moveY = 0
    
       if (this.dragItem.isDrag === true) {
          moveX = e.pageX  - borderDiv.offsetLeft - 20  //元素的横坐标 = 鼠标坐标 - 图片左侧位置 - 圆的半径
          moveY = e.pageY  - borderDiv.offsetTop - 20   //元素的纵坐标 = 鼠标坐标 - 图片上侧位置 - 圆的半径
         // 范围限定
          moveX > 0 && moveX < borderDiv.offsetWidth
          moveY > 0 && moveY <  borderDiv.offsetHeight
         let maxX =  borderDiv.offsetWidth -20
         let maxY =  borderDiv.offsetHeight
         moveX = Math.min(maxX, Math.max(0, moveX)) 
         moveY = Math.min(maxY, Math.max(0, moveY))
         // 范围限定结束
         dragDiv.style.left = moveX +  'px'
         dragDiv.style.top = moveY + 'px'
         console.log( '移动=' + moveY, '鼠标----》='+ e.pageY,  'borderDiv.offsetTop=' + borderDiv.offsetTop, moveY * 100 / borderDiv.offsetHeight + '%')
         this.dragItem.position = {
           x : moveX * 100 / borderDiv.offsetWidth + '%',
           y : moveY * 100 / borderDiv.offsetHeight + '%',
           x1: (moveX + 20) * 100 /borderDiv.offsetWidth + '%',
           y1: (moveY + 20) * 100 /borderDiv.offsetHeight + '%'
         }
       }
     },
    

    离开

    当监听事件捕捉到鼠标抬起离开时候激活该函数

  7. upDatedDragList将标记好位置保存

  8. dragItem将其值置空
  9. 移除两个监听事件,防止内存溢出

    onMouseUp(e) {
       this.upDatedDragList()
       //this.dragItem.isDrag = false //标记是否可拖拽
       this.dragItem = {}
        document.removeEventListener('mousemove', this.onMouseMove, true)
       document.removeEventListener('mouseup', this.onMouseUp, true)
     },
    

    源码解析

    ```javascript

<a name="AjR8n"></a>
## 进阶优化版
之前的初级版有一个致命缺陷,它只适合在可拖拽区域在一屏内,如果元素高度超过拖拽区域通过滚动条,往下拖动就会出现鼠标跳出的问题

<a name="yXJ0B"></a>
### 按下事件优化
layerX是个好东西,可以好好看下,这是相对于父元素的偏移量的计算,这样就补我我们去手动计算
```javascript
   // 计算鼠标相对拖拽元素的左上角坐标,并标记拖动
    onMousedown(e, key) {
      let dragDiv = this.selectDom('drag' + key)
      let borderDiv = this.selectDom('ai-drawer')
      if (!dragDiv || !borderDiv) return
      this.dragItem = {
        mouseOffsetX: e.layerX,
        mouseOffsetY: e.layerY,
        isDrag: true,
        tagName: key
      }
      document.addEventListener('mousemove', this.onMouseMove, true)
      document.addEventListener('mouseup', this.onMouseUp, true)
    },

拖动事件优化

    onMouseMove(e) {
      let borderDiv = this.selectDom('ai-drawer')
      let dragDiv = this.selectDom('drag' + this.dragItem.tagName)
      if (!dragDiv || !borderDiv) return
      if (this.dragItem.isDrag === true) {
        let moveX = e.layerX - 20
        let moveY = e.layerY - 20
        let maxX = borderDiv.offsetWidth
        let maxY = borderDiv.offsetHeight
        // console.log(e.layerX, e.layerY, 'update', e.target.id)
        // 处理冒泡事件导致的问题
        if (e.target.id === 'draw-pic') {
          moveX = Math.min(maxX, Math.max(0, moveX)) 
          moveY = Math.min(maxY, Math.max(0, moveY))
          dragDiv.style.left = moveX * 100 / borderDiv.offsetWidth + '%'
          dragDiv.style.top = moveY * 100 / borderDiv.offsetHeight + '%'

          this.dragItem.position = {
            x: moveX * 100 / borderDiv.offsetWidth + '%',
            y: moveY * 100 / borderDiv.offsetHeight + '%',
            x1: (moveX + 20) * 100 / borderDiv.offsetWidth + '%',
            y1: (moveY + 20) * 100 / borderDiv.offsetHeight + '%'
          }
        }
      }
    },