原生JS的drag事件实现元素拖拽的bug

项目场景:

在网上看见很多jQuery和Vue实现元素拖拽的方法,心想如果使用原生JS该如何来来实现。
我的实现目标是当拖拽一个div元素,可以让元素跟着鼠标移动


问题描述:

开门就遇见一个bug。。

window.onload = () => {
  const draggable = document.getElementById("draggable");
  draggable.addEventListener("dragstart", handleDragstart);
  let distanceX = 0;
  let distanceY = 0;

  function handleDragstart(e) {

    //获取鼠标位置
    const x = e.clientX;
    const y = e.clientY;
    console.log(x, y);
    //获取draggable的位置
    const offsetX = e.target.offsetLeft;
    const offsetY = e.target.offsetTop;
    console.log(offsetX, offsetY);
    //保存鼠标悬停处与draggable边界的距离(是全局变量)
    distanceX = x - offsetX;
    distanceY = y - offsetY;
    console.log(distanceX, distanceY);
  }

  draggable.addEventListener("drag", handleDrag);

  function handleDrag(e) {
    //获取实时鼠标位置
    let x = e.clientX;
    let y = e.clientY;
    console.log(x, y);
    // 计算出draggable的坐标
    let left = x - distanceX;
    let top = y - distanceY;
    console.log(left, top);
    // //设置draggable坐标
    draggable.style.left = left + "px";
    draggable.style.top = top + "px";
  }
}

结果一松手就弹到左上角
在这里插入图片描述
查看控制台,哦,原来是
在这里插入图片描述
在拖拽鼠标松开的一瞬间,鼠标坐标e.clientX和Y都变成了0,导致计算left和top时变成了负数,所以一松手就弹到左上角了。趁我没注意啊,我大意了啊。
在这里插入图片描述

然后我想通过百度去找寻答案,结果因为问题难以描述所以没有找到答案,否则有解决方案了我也就懒得写文章了。

解决方案

大家学过drag事件群就知道,拖拽事件中还有个dragend事件,我尝试添加dragend事件监听

draggable.addEventListener("dragend", handleDragend);

  function handleDragend(e) {
    //获取实时鼠标位置
    let x = e.clientX;
    let y = e.clientY;
    console.log(x, y);
  }

结果正确打印出松手一瞬间的鼠标坐标
在这里插入图片描述
尝试在dragend再次修改一次坐标

draggable.addEventListener("dragend", handleDragend);

  function handleDragend(e) {
    //获取实时鼠标位置
    let x = e.clientX;
    let y = e.clientY;
    console.log(x, y);
    // 计算出draggable的坐标
    let left = x - distanceX;
    let top = y - distanceY;
    // //设置draggable坐标
    draggable.style.left = left + "px";
    draggable.style.top = top + "px";
  }

在这里插入图片描述
不考虑复杂情况,问题在这里已经解决了。


情景扩展

边界情况

虽然动画很流畅,也没有一瞬间闪到左上角的情况,但是实际上元素的确经过左上角
是弹回去后再过来,我可以用一个触发器来验证

if (draggable.offsetLeft < 0) {
      draggable.style.backgroundColor = "#ff0055"
    }

在这里插入图片描述
为了避免隐患,之后可能会进行一些边界判断,所以我使用了个笨方法(如果有懂为什么松手鼠标坐标会变成0的同学可以在评论区告诉我)

在之前的handleDrag函数(“drag”拖拽事件)中添加if判断。(这样设置一举两得,既可以避免元素弹回左上角,还能让元素不超过边界)

// //设置draggable坐标
    if (left < 0 || top < 0) {
      return;
    }
    draggable.style.left = left + "px";
    draggable.style.top = top + "px";

在这里插入图片描述
虽然问题解决了,但还是不难看出一个问题,不流畅啊!!!
那是因为一碰到边界就阻止设置元素坐标了,如果x轴碰到边界,y轴上也无法移动了。
所以我们分开判断,将前面的改写成

// //设置draggable坐标
    if (left >= 0) {
      draggable.style.left = left + "px";
    }
    if (top >= 0) {
      draggable.style.top = top + "px";
    }

在这里插入图片描述
问题虽然解决了,但还是不难发现一个问题,当快速拖拽超出屏幕时,元素无法贴上边界,这里就是之前各位肯定听教程讲过的“吸边“:
当元素超过边界时,手动将其坐标设置为边界值
为了方便,我先计算出右边边界和下面边界的值

  const maxTop = document.body.clientHeight - draggable.offsetHeight;
  console.log(maxTop);
  const maxLeft = document.body.clientWidth - draggable.offsetWidth;
  console.log(maxLeft);

然后将前面的代码替换成如下

  // //设置draggable坐标
    if (left >= maxLeft) { //当超过右边界
      draggable.style.left = maxLeft + "px";
    } else if (left < 0) { //当超过左边界
      draggable.style.left = "0px";
    } else {
      draggable.style.left = left + "px";
    }

    if (top >= maxTop) { //当超过下边界
      draggable.style.top = maxTop + "px";
    } else if (top < 0) { //当超过上边界
      draggable.style.top = "0px"
    } else {
      draggable.style.top = top + "px"
    }

那么问题就这样解决了。


补充:

相信跟着我做的同学一定发现了一个问题

一开始我只以为是字段缺失,实际上sex字段根本不对应,这是我以前的老数据,我怀疑数据没有更新,然后我试着把json数据全删掉保存,然后尝试下拉刷新请求数据

在这里插入图片描述

在这里插入图片描述
这是怎么回事?发生甚磨事了?
我一看,哦,原来是:
maxTop = document.body.clientHeight - draggable.offsetHeight 等于 0 了
在这里插入图片描述
问题原因很简单,我没有在div外面套一层全屏容器,导致当前的body高度和div高度相同,也就是说body完全由div撑开其高度。
解决办法很简单,外面套一层全屏容器或者把body的宽高都改成100vh / 100vw就ok了

body {
  width: 100vw;
  height: 100vh;
}

但你以为这就解决问题了吗?在这里插入图片描述
做到这里虽然还能通过修改代码做一部分改进,但是我已经不想改了,为什么?

因为一旦拖拽元素的鼠标指针超过浏览器边界,就会立刻像之前松开鼠标一样直接弹回左上角,如果说我可以再次通过判断鼠标超出浏览器来阻止触发坐标修改为(0,0)从而阻止其弹回左上角。但是我们已经做了很多步骤了,相同的实现方法有通过mousemove事件实现同样的功能简单而且没有bug,那为什么还要通过drag事件来实现拖拽移动呢?

完整代码贴出:

window.onload = () => {
  const draggable = document.getElementById("draggable");
  draggable.addEventListener("dragstart", handleDragstart);
  let distanceX = 0;
  let distanceY = 0;
  const maxTop = document.body.clientHeight - draggable.offsetHeight;
  console.log(maxTop);
  const maxLeft = document.body.clientWidth - draggable.offsetWidth;
  console.log(maxLeft);

  function handleDragstart(e) {

    //获取鼠标位置
    const x = e.clientX;
    const y = e.clientY;
    console.log(x, y);
    //获取draggable的位置
    const offsetX = e.target.offsetLeft;
    const offsetY = e.target.offsetTop;
    console.log(offsetX, offsetY);
    //保存鼠标悬停处与draggable边界的距离(是全局变量)
    distanceX = x - offsetX;
    distanceY = y - offsetY;
    console.log(distanceX, distanceY);
  }

  draggable.addEventListener("drag", handleDrag);

  function handleDrag(e) {
    //获取实时鼠标位置
    let x = e.clientX;
    let y = e.clientY;
    console.log(x, y);

    // 计算出draggable的坐标
    let left = x - distanceX;
    let top = y - distanceY;
    console.log(left, top);

    // //设置draggable坐标
    if (left >= maxLeft) { //当超过右边界
      draggable.style.left = maxLeft + "px";
    } else if (left < 0) { //当超过左边界
      draggable.style.left = "0px";
    } else {
      draggable.style.left = left + "px";
    }

    if (top >= maxTop) { //当超过下边界
      draggable.style.top = maxTop + "px";
    } else if (top < 0) { //当超过上边界
      draggable.style.top = "0px"
    } else {
      draggable.style.top = top + "px"
    }
  }

//添加松手事件判断
  draggable.addEventListener("dragend", handleDragend);

  function handleDragend(e) {
  //检测是否超出左边界(是就变红)
    if (draggable.offsetLeft < 0) {
      draggable.style.backgroundColor = "#ff0055"
    }
    //再次调用一次拖拽处理函数,把元素从左上角拉回来
    handleDrag(e);
  }

}

不推荐使用drag进行元素拖拽移动的原因

因此,我总结了几条不推荐使用drag来做元素拖拽移动原因:
1.动画不流畅有明显残影,drag拖拽帧率很低(对比mousemove就能明显发现)
2.在松手的一瞬间会将鼠标位置设置为(0,0)从而导致元素弹回左上角
3.当拖拽离开浏览器时,元素仍然弹回左上角,鼠标坐标变成(0,0),(但事件没有中断)

所以以后遇到这类问题还是老老实实用mouseover来实现吧

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值