原生JS实现拖拽效果及潜在BUG分析(附动图)

1,741 阅读4分钟

    我按思考顺序写一下,想看最终效果的直接拉动到最后~

    一开始我先写出了这个代码~

<!DOCTYPE html><html lang="en"><head>  <meta charset="UTF-8">  <meta name="viewport" content="width=device-width, initial-scale=1.0">  <meta http-equiv="X-UA-Compatible" content="ie=edge">  <title>Document</title>  <style>    body{      user-select: none;    }    .a,    .b,    .c {      position: absolute;      width: 100px;      height: 100px;    }    .a {      background: red;    }    .b {      background: blue;    }    .c {      background: green;    }  </style></head><body>  <div style="left:0;top:0" class="draggable a"></div>  <div style="left:0;top:0" class="draggable b"></div>  <div style="left:0;top:0" class="draggable c"></div>  <script>    var maxIndex = 2;    window.addEventListener('mousedown', e => {      if (e.target.matches('.draggable')) {        var movingEl = e.target        movingEl.style.zIndex = maxIndex++        var lastX = e.pageX        var lastY = e.pageY        window.addEventListener('mousemove', function move(e) {          if (e.buttons == 0) {            window.removeEventListener('mousemove', move)          }          var diffX = e.pageX - lastX          var diffY = e.pageY - lastY          lastX = e.pageX          lastY = e.pageY          movingEl.style.left = parseInt(movingEl.style.left) + diffX + 'px'          movingEl.style.top = parseInt(movingEl.style.top) + diffY + 'px'        })      }    })  </script></body></html>


    可以看到,初步效果是实现了的,我这里使用了事件代理,这样的话在浏览器里直接添加元素,也能够进行拖拽,而且也减少了DOM绑定事件。

    CSS方面,user-select:none这个属性是防止鼠标变成  🚫(禁止) 这个图标,从而导致无法选中。

    JS方面,要在mousemove事件下注意保存上次事件发生时的鼠标坐标,即为lastX与lastY。用e.buttons来判断是否中止拖拽,贴一段MDN的代码:

    只读属性MouseEvent.buttons指示事件触发时哪些鼠标按键被按下。

    每一个按键都用一个给定的数(见下文)表示。如果同时多个按键被按下,buttons 的值为各键对应值做与计算(+)后的值。例如,如果右键(2)和滚轮键(4)被同时按下,buttons 的值为 2 + 4 = 6。

  • 0 : 没有按键或者是没有初始化
  • 1 : 鼠标左键
  • 2 : 鼠标右键
  • 4 : 鼠标滚轮或者是中键
  • 8 : 第四按键 (通常是“浏览器后退”按键)
  • 16 : 第五按键 (通常是“浏览器前进”)


    但是,这样子写看起来是实现了,但是还是有BUG的,比如:



    竟然可以拖动到窗口外面,这被测试发现不就是BUG嘛!!!赶紧修改一下JS

var maxIndex = 2;    window.addEventListener('mousedown', e => {      if (e.target.matches('.draggable')) {        var movingEl = e.target        movingEl.style.zIndex = maxIndex++        var lastX = e.pageX        var lastY = e.pageY        window.addEventListener('mousemove', function move(e) {          if (e.buttons == 0) {            window.removeEventListener('mousemove', move)          }          var diffX = e.pageX - lastX          var diffY = e.pageY - lastY          lastX = e.pageX          lastY = e.pageY          var left = parseInt(movingEl.style.left) + diffX          var top = parseInt(movingEl.style.top) + diffY                    if (left < 0) {            left = 0          }else if (left > innerWidth - 100) {            left = innerWidth - 100          }          if (top < 0) {            top= 0          }else if (top > innerHeight - 100) {            top = innerHeight - 100          }          movingEl.style.left = left + 'px'          movingEl.style.top = top + 'px'                  })      }    })

    这样虽然拖不到视窗外面了,但是又发现了新BUG。。。


    虽然元素移不出视口了,但是,第一种BUG,当鼠标在视口外并且处于按下状态,向视口内移动时,还没等到鼠标移动进视口,里面的元素倒是先动起来了。第二种BUG,鼠标在视口外松开,移动到视口内时,元素会突然跳变。

    第一种情况,鼠标移动到视口外时,left本来也是同步增加的,但是因为我们设置了if语句让元素跑不出视口。而鼠标在视口外向视口内移动,元素也同步往相同方向移动,其实是正确的。所以我们的目的是让鼠标移到窗口内的元素时再进行移动。

    第二种情况,由于在视口外松开鼠标,mousemove并不会触发,但是这里的程序会记录最后一次mousemove时的lastX和lastY,当鼠标从视口外再次移动到视口内时,mousemove事件最后一次在视口内触发(之后就被解绑),e.pageX和e.pageY会最后一次读取鼠标所在的位置,与之前在视口外记录的lastX和lastY相减得到负数,因此元素的left和top值就会变小,也就一下子发生了跳变。

    因此,针对第一种情况,我们改变一下策略,记录鼠标的初始位置和元素的初始位置。元素移动的距离就是  :

元素的初始位置+鼠标相对鼠标本身初始位置的距离  

第二种情况,我们之前都是在mousemove事件里触发解绑事件,而在视口外松开鼠标,mousemove又不会触发,造成解绑不及时,所以我们再绑定一个mouseup事件用来解绑。

代码修改如下:

var maxIndex = 2;    window.addEventListener('mousedown', e => {      if (e.target.matches('.draggable')) {        var movingEl = e.target        movingEl.style.zIndex = maxIndex++        var mouseInitX = e.pageX        var mouseInitY = e.pageY        var elInitX = parseInt(movingEl.style.left)        var elInitY = parseInt(movingEl.style.top)        var move        window.addEventListener('mousemove', move = function (e) {          if (e.buttons == 0) {            window.removeEventListener('mousemove', move)          }          var diffX = e.pageX - mouseInitX          var diffY = e.pageY - mouseInitY          var left = elInitX + diffX          var top = elInitY + diffY                    if (left < 0) {            left = 0          }else if (left > innerWidth - 100) {            left = innerWidth - 100          }          if (top < 0) {            top= 0          }else if (top > innerHeight - 100) {            top = innerHeight - 100          }          movingEl.style.left = left + 'px'          movingEl.style.top = top + 'px'                  })        window.addEventListener('mouseup', e => {          if (e.buttons == 0) {            window.removeEventListener('mousemove', move)          }        })      }    })


     这样就好了~~



---------------分割线---------------

    我们还可以修改一下if判断做一个有趣的东西~

if (left < 100) {     left = 0   }else if (left > innerWidth - 100) {     left = innerWidth - 100}if (top < 100) {     top= 0   }else if (top > innerHeight - 100) {     top = innerHeight - 100}


    相当于一个磁铁吸附的感觉,相信这个功能大家肯定遇到过吧~