js 实现拖拽元素
原理:实现拖拽效果其实就是通过监听鼠标移动事件,实时改变元素的位置。所以重点就是 进行位置的计算。
位置计算原理:

因为需要计算元素的位置变化,所以需要掌握几个关于位置的api

api

描述

offsetTop

元素到offsetParent顶部的距离,距离元素最近的一个具有定位的祖宗元素(relative,absolute,fixed),若祖宗都不符合条件,offsetParent为body。

offsetLeft

元素到offsetParent左边的距离,距离元素最近的一个具有定位的祖宗元素(relative,absolute,fixed),若祖宗都不符合条件,offsetParent为body。

clientY

当鼠标事件发生时,鼠标相对于浏览器(这里说的是浏览器的有效区域)y轴的位置;

clientX

当鼠标事件发生时,鼠标相对于浏览器(这里说的是浏览器的有效区域)x轴的位置;

计算公式:

点击需要拖动元素时,获取该元素的初始位置。

//获取x坐标和y坐标
x = e.clientX;
y = e.clientY;

//获取左部和顶部的偏移量
l = dv.offsetLeft;
t = dv.offsetTop;

鼠标移动期间 获取元素当前的位置信息

nx = e.clientX; // 当前鼠标距离屏幕X的距离
ny = e.clientY; // 当前鼠标距离屏幕Y的距离

计算

//计算移动后的左偏移量和顶部的偏移量
var nl = nx - (x - l); //(x - l) 得到鼠标距离元素左边边框的距离
var nt = ny - (y - t); //(y - t) 得到鼠标距离元素上边边框的距离

进行事件监听

var dv = document.getElementById('dv');
//鼠标按下事件
  dv.onmousedown = function(e) {
      //获取x坐标和y坐标
      x = e.clientX;
      y = e.clientY;

      //获取左部和顶部的偏移量
      l = dv.offsetLeft;
      t = dv.offsetTop;
      //开关打开
      isDown = true;
      //设置样式
      dv.style.cursor = 'move';
  }
//鼠标移动
  dv.onmousemove = function(e) {
      if (isDown == false) {
          return;
      }
      //获取x和y
      var nx = e.clientX;
      var ny = e.clientY;
      //计算移动后的左偏移量和顶部的偏移量
      var nl = nx - (x - l);
      var nt = ny - (y - t);
      dv.style.left = nl + 'px';
      dv.style.top = nt + 'px';
  }
//鼠标抬起事件
  dv.onmouseup = function() {
      //开关关闭
      isDown = false;
      dv.style.cursor = 'default';
  }
会出现的问题:
问题1: 拖动太快 鼠标丢失

拖拽的实现原理:通过事件mousedown(事件的触发) →mousemove(事件的控制) →mouseup(事件的清除),拖拽的过程就是mousemove阶段;

问题产生的原因:因为mousemove 的间隔性触发,当两次mousemove事件触发的间隔中,鼠标移动距离出了element的范围,就会产生鼠标脱离element范围,拖拽就停止,

解决方法: 将mousemove事件挂在docment,而不是对应的element,此时鼠标滑动只要不出docment范围就不会触发上述情况。

//鼠标移动
  document.onmousemove = function(e) {
      if (isDown == false) {
          return;
      }
      //获取x和y
      var nx = e.clientX;
      var ny = e.clientY;
      //计算移动后的左偏移量和顶部的偏移量
      var nl = nx - (x - l);
      var nt = ny - (y - t);
      dv.style.left = nl + 'px';
      dv.style.top = nt + 'px';
  }
//鼠标抬起事件
  document.onmouseup = function() {
      //开关关闭
      isDown = false;
      dv.style.cursor = 'default';
  }
问题2: 拖动事件与元素的点击事件有冲突

拖动事件完成的动作时是:mousedown(事件的触发) →mousemove(事件的控制) →mouseup(事件的清除) 但是mouseup的时候 同时会触发 点击事件(如果元素上面有点击事件的话)

处理办法:记录mousedown(记录开始时间) →mousemove→mouseup(记录结束时间) 的时间 根据时间长短判断是进行了点击事件还是进行了拖拽事件。

//鼠标按下事件
  dv.onmousedown = function(e) {
      //获取x坐标和y坐标
      x = e.clientX;
      y = e.clientY;

      //获取左部和顶部的偏移量
      l = dv.offsetLeft;
      t = dv.offsetTop;
      //开关打开
      isDown = true;
      //设置样式
      dv.style.cursor = 'move';
    	startTime = new Date().getTime(); // 点击开始时间
  }
//鼠标抬起事件
  dv.onmouseup = function() {
      //开关关闭
      clickFlag = endTime - startTime < 200;
      isDown = false;
      dv.style.cursor = 'default';
  }
// 元素的点击事件
 const handleUserEnterClick = () =>{
   if (!clickFlag) return; // 如果是拖拽事件 则不执行click事件
   .....
   ....
   ...
 }
问题三:边缘问题 元素可以拖拽出屏幕范围之外。

正常需求的话 就希望拖拽元素只在屏幕的可视范围内进行拖拽,不能跑出去。

在onmousemove 中添加边缘控制就好,具体范围可以根据具体需求更改。

// 控制左右范围
      if(nl < 0){
        dv.style.left ='0px';
      }else if(nl > document.body.clientWidth - 85){
        dv.style.left = document.body.clientWidth - 85 + 'px';
      }else{
        dv.style.left = nl + 'px';
      }
      // 控制上下
      if(nt < 0){
        dv.style.top ='0px';
      }else if(nt > document.body.clientHeight - 100){
        dv.style.top = document.body.clientHeight - 100 + 'px';
      }else{
        dv.style.top = nt + 'px';
      }
完整的一个小demo 例子
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="dv"></div>
</body>
</html>
<script>
  //获取元素
  var dv = document.getElementById('dv');
  var x = 0;
  var y = 0;
  var l = 0;
  var t = 0;
  var isDown = false;
  //鼠标按下事件
  dv.onmousedown = function(e) {
      //获取x坐标和y坐标
      x = e.clientX;
      y = e.clientY;

      //获取左部和顶部的偏移量
      l = dv.offsetLeft;
      t = dv.offsetTop;
      //开关打开
      isDown = true;
      //设置样式
      dv.style.cursor = 'move';
  }
  //鼠标移动
  document.onmousemove = function(e) {
      if (isDown == false) {
          return;
      }
      //获取x和y
      var nx = e.clientX;
      var ny = e.clientY;
      //计算移动后的左偏移量和顶部的偏移量
      var nl = nx - (x - l);
      var nt = ny - (y - t);
      // 控制左右范围
      if(nl < 0){
        dv.style.left ='0px';
      }else if(nl > document.body.clientWidth - 85){
        dv.style.left = document.body.clientWidth - 85 + 'px';
      }else{
        dv.style.left = nl + 'px';
      }
      // 控制上下
      if(nt < 0){
        dv.style.top ='0px';
      }else if(nt > document.body.clientHeight - 100){
        dv.style.top = document.body.clientHeight - 100 + 'px';
      }else{
        dv.style.top = nt + 'px';
      }
  }
  //鼠标抬起事件
  document.onmouseup = function() {
      //开关关闭
      isDown = false;
      dv.style.cursor = 'default';
  }
</script>
<style>
   #dv {
    width:100px;
    height:100px;
    background-color:pink;
    border-radius:50%;
    position:absolute;
  }
  html,body{
    height: 100%;
  }
</style>