进阶教程 10. DOM 事件扩展-拖拽

994 阅读5分钟

这是我参与8月更文挑战的第17天,活动详情查看:8月更文挑战

一、基础拖拽

  • 需求: 页面中的盒子dragObj,我们需要它有拖拽功能;

    1. 点击这个盒子,鼠标左键不松开
    2. 按住鼠标左键不松开移动鼠标,在鼠标移动时盒子要跟着盒子移动;
    3. 当我拖动到目的地(我想停止拖拽的地方)松开鼠标左键,盒子就要停在松开鼠标的位置;
    4. 当我松开鼠标后,无论我怎么动鼠标,盒子都不能跟着动了
  • 实现思路:我们分析需求中的拖拽阶段,发现移动分为三个阶段

    1. 鼠标按下时赋予这个盒子可以被拖动的能力(鼠标动盒子跟着动);
    2. 拖动其实就是鼠标移动,盒子跟随,即在mousemove事件中,实现鼠标跟随;
    3. 松开鼠标左键,移动盒子的可以被拖动的能力 综上,在鼠标按下时,即mousedown事件触发时,给元素绑定mousemove事件;在mousemove事件中实现鼠标跟随。最后鼠标抬起即mouseup事件触发,在mouseup事件函数中取消可以被拖动的能力(在mouseup时移除mousemove事件)
  • html 代码

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>Title</title>
   <style>
      * {
         margin: 0;
         padding: 0;
      }
      .dragObj {
         position: absolute;
         width: 200px;
         height: 200px;
         background: red;
      }
      .box {
         width: 300px;
         height: 300px;
         background: #00b38a;
      }
   </style>
</head>
<body>
<div class="dragObj" id="dragObj" style="left: 100px; top: 100px"></div>
<div class="box" id="box"></div>

<script src="js/6-发布定阅准备.js"></script>
</body>
</html>
  • JS
let dragObj = document.getElementById('dragObj');

dragObj.onmousedown = dragStart; // 监听盒子的鼠标按下事件,在事件函数中赋予盒子可以被拖动的能力;
dragObj.onmouseup = dragEnd; // 鼠标抬起结束拖拽
function dragStart(e) {
   // 开始拖拽
   // 1. 记录盒子初始位置、鼠标按下的位置
   this.startL = parseFloat(this.style.left);
   this.startT = parseFloat(this.style.top);

   // 2. 记录鼠标按下时的鼠标位置
   this.startX = e.pageX;
   this.startY = e.pageY;

   // 3. 赋予元素可以被拖拽的能力
   dragObj.onmousemove = dragMove;

}

function dragMove(e) {
   // 拖动:在拖动过程中不断的计算鼠标现在所处的位置相对于鼠标按下的位置移动的距离,然后加上盒子的初始位置,就是盒子应该出现的位置

   // 1. 计算当前鼠标位置相对于鼠标按下移动的距离
   let moveX = e.pageX - this.startX;
   let moveY = e.pageY - this.startY;

   // 2. 计算盒子应该出现的位置

   let curL = this.startL + moveX;
   let curT = this.startT + moveY;

   // 3. 将盒子的left和top分别设置为它应该出现的值
   this.style.left = `${curL}px`;
   this.style.top = `${curT}px`;

}

function dragEnd() {
   dragObj.onmousemove = null;
}

二、解决鼠标丢失

2.1 鼠标丢失的原因:

当鼠标移动的时候,因为浏览器计算盒子应该出现的位置是需要一定时间的。如果在计算的这一段时间内再次移动鼠标,因为上一次mousemove时盒子的位置还没计算出来,所以盒子没办法去到该去的位置,这时鼠标又去了一个新位置,所以盒子就更跟不上了,所以就出现了鼠标把盒子丢失了的现象;丢失元素后,即便鼠标再移动,但是不是在元素上移动的了,所以无法触发元素的onmousemove事件了,所以元素也就不会再追随鼠标了; 丢失元素后再抬起鼠标左键,触发的也不是盒子的mouseup事件,所以盒子的跟随鼠标移动的能力也没能被移除,这就导致当鼠标再次回到盒子上时,盒子还能跟着动;

2.2 解决方案:

  1. 将元素和鼠标绑定在一起 setCapture(),当拖拽结束后再解绑 releaseCapture() 【Chrome不兼容、IE 和 ff可以用】
  2. 因为鼠标不管怎么动,都出不了浏览器页面,所以我们把元素的mousemove和mouseup事件绑定给document;(采用事件委托的思想解决问题)

let dragObj = document.getElementById('dragObj');

dragObj.onmousedown = dragStart;
dragObj.onmouseup = dragEnd;
function dragStart(e) {
   // this.setCapture();
   this.startX = e.pageX;
   this.startY = e.pageY;

   this.startL = parseFloat(this.style.left);
   this.startT = parseFloat(this.style.top);


   document.onmousemove = dragMove.bind(this);
}

function dragMove(e) {
   let moveX = e.pageX - this.startX;
   let moveY = e.pageY - this.startY;

   let curL = this.startL + moveX;
   let curT = this.startT + moveY;

   this.style.left = curL + 'px';
   this.style.top = curT + 'px';
}

function dragEnd(e) {
   // this.releaseCapture()
   document.onmousemove = null;
}

三、原生拖放

HTML5原生支持拖拽,但是需要给被拖动元素设置其行内属性 draggable = true;

  • HTML代码:
<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>Title</title>
   <style>
      #litBox {
         position: absolute;
         width: 100px;
         height: 100px;
         background: red;
      }
      #bigBox {
         width: 300px;
         height: 300px;
         border: 1px solid #000;
         margin: 50px auto;
      }
      #fileBox {
         margin: 30px auto;
         width: 300px;
         height: 300px;
         border: 3px dashed #00b38a;
         border-radius: 5px;
         text-align: center;
      }
      #fileBox:before {
         content: '+';
         font-size: 200px;
         text-align: center;
         line-height: 250px;
         color: #333333;
      }
   </style>
</head>
<body>
<div class="fileBox" id="fileBox"></div>
<div id="litBox" draggable="true"></div>
<div id="bigBox"></div>



<script src="js/5-dataTransfer对象.js"></script>
</body>
</html>

3.1 原生拖拽实现也是基于原生的拖放事件

 let litBox = document.getElementById('litBox');
  let bigBox = document.getElementById('bigBox');

3.2 ondragstart

  • 开始拖拽

  • 按下鼠标并移动鼠标就会触发

  litBox.ondragstart = function (e) {
   console.log('start');
   e.dataTransfer.setData('Text', this.id);
  };

3.3 ondrag

  • 拖动的过程中触发
 litBox.ondrag = function () {
   // 拖动的过程中触发
   console.log('dragging')
  };

3.4 ondragend

  • 拖拽结束触发
  litBox.ondragend = function () {
   console.log('end')
  };

  • 以上三个都是相对于被拖动的元素;

  • 以下事件都是相对于放置元素的:

3.5 ondragover

  • 当被拖拽的元素经过bigBox时会触发bigBox的ondragover事件
  bigBox.ondragover = function (e) {
   // 当被拖拽的元素经过bigBox时会触发bigBox的ondragover事件
   console.log('over');
   this.style.backgroundColor = 'blue';
   e.preventDefault();
  };

3.6 ondragleave

  • 当被拖拽的元素经过离开bigBox时触发

  bigBox.ondragleave = function () {
   // 当被拖拽的元素经过离开bigBox时触发
   this.style.backgroundColor = '';
   console.log('leave')
  };

ondrop

  • 当被拖拽的元素在bigBox上松开鼠标时触发
  bigBox.ondrop = function (e) {
   // 当被拖拽的元素在bigBox上松开鼠标时触发
   console.log('drop');
   console.log(e.dataTransfer.getData('Text'))
   let id = e.dataTransfer.getData('Text');
   bigBox.appendChild(document.getElementById(id))
  };

四、dataTransfer对象

在原生的拖拽事件中,可以自定义存储数据; 在拖拽的事件对象中有一个 dataTransfer对象,基于这个对象可以自定义存储或者读取数据;

4.1 存储:

  • dataTransfer.setData('key', 'value')

4.2 获取:

  • dataTransfer.getData('key')
let litBox = document.getElementById('litBox');
let bigBox = document.getElementById('bigBox');

litBox.ondragstart = function (e) {
   // 在拖拽的时候需要设置元素id
   e.dataTransfer.setData('id', this.id);
};

bigBox.ondragover = function (e) {
   // 在drageover中阻止默认行为,否则无法触发ondraop事件
   e.preventDefault();
};

bigBox.ondrop = function (e) {
   let id = e.dataTransfer.getData('id'); // 通过e.dataTransfer对象的getData获取被拖拽元素的id
   console.log(id)
};

// 此外,如果是拖拽文件到放置目标元素中,在 dataTransfer.files 中存储了这些文件的信息;
let fileBox = document.getElementById('fileBox');
fileBox.ondragover = function (e) {
   e.preventDefault();
};
fileBox.ondrop = function (e) {
   console.log(e.dataTransfer.files);
   // name: 带拓展名文件名
   // size: 文件大小
   // type: 'text/plain'
   e.preventDefault();
};

五、原生拖放示例

原生拖拽实现也是基于原生的拖放事件

  • 将页面中的小盒子litBox拖动到大盒子中
  1. 在拖拽的时候需要设置元素id;
  2. 在drageover中阻止默认行为,否则无法触发ondraop事件;
  3. 通过e.dataTransfer对象的getData获取被拖拽元素的id;
  4. 修改 DOM 节点关系;


let litBox = document.getElementById('litBox');
let bigBox = document.getElementById('bigBox');

litBox.ondragstart = function (e) {

   e.dataTransfer.setData('id', this.id);
};

bigBox.ondragover = function (e) {
   
   e.preventDefault();
};
bigBox.ondrop = function (e) {
   let id = e.dataTransfer.getData('id'); // 

   let ele = document.getElementById(id);
   this.appendChild(ele);
   e.preventDefault();
};