Drag and Drop API 实现 JavaScript 中的原生拖放功能

在这里插入图片描述

理解什么是拖放,我们先做个简单的实验。鼠标移动到页面左上角“优快云” 图片上方,点击左键不放开,拖动鼠标,发现图片随着鼠标移动,松开鼠标时,图片消失。

一、拖放(Drag and Drop)有什么作用?

在 JavaScript 中,拖放(Drag and Drop)是一种用户界面交互模式,允许用户通过鼠标选择、拖动和放置元素。

常见拖放场景,如文件上传、画布编辑器、列表排序等。我们看一个文件上传例子:

<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>文件上传</title>
    <style>
      #dropzone {
        margin: 0 auto;
        width: 300px;
        height: 200px;
        border: 4px dashed #ccc;
        display: flex;
        justify-content: center;
        align-items: center;
        color: #ccc;
        font-size: 16px;
      }
    </style>
  </head>
  <body>
    <div id="dropzone">将文件拖到这里</div>
    <script>
      //拖放目标
      const dropzone = document.getElementById('dropzone');
      //拖放目标-dragenter事件
      dropzone.addEventListener('dragenter', (event) => {
        event.preventDefault();
        console.log('拖放目标 - dragenter事件');
        dropzone.style.backgroundColor = 'darkgreen';
      });
      //拖放目标-dragover事件
      dropzone.addEventListener('dragover', (event) => {
        event.preventDefault();
        dropzone.style.borderColor = 'green';
        dropzone.innerText = 'dragover';
      });
      //拖放目标-dragleave事件
      dropzone.addEventListener('dragleave', () => {
        dropzone.style.borderColor = '#ccc';
        dropzone.style.backgroundColor = '#fff';
        dropzone.innerText = '将文件拖到这里';
      });
      //拖放目标-drop事件
      dropzone.addEventListener('drop', (event) => {
        event.preventDefault();
        dropzone.style.borderColor = '#ccc';
        dropzone.innerText = 'drop';
        const files = event.dataTransfer.files;
        for (let i = 0; i < files.length; i++) {
          console.log('文件名:', files[i].name); //F12查看 文件名: ***
        }
      });
    </script>
  </body>
</html>

拖放是 2 个动作,拖(Drag)和放(Drop),拖动可拖动元素,放在放置目标上面。

二、拖放元素

在 HTML 中,默认可拖放的元素主要包括文本、图片和链接(即标签)。这些元素在默认情况下具有draggable="true"属性,因此用户可以直接选中并拖动它们。其他非默认可拖放的元素,可以通过设置draggable="true"属性来使它们变得可拖放.

  • 文本:选中文本才能拖动。选中的文本为拖动数据,可以通过 e.dataTransfer.getData('text')来获取。
  • 图片:按住鼠标左键选中图片开始拖动。图片的 URL 为拖动数据,可以通过 e.dataTransfer.getData('url')来获取
  • 链接:按住鼠标左键选中并开始拖动。链接的 URL 和文本为拖动数据,可以通过 e.dataTransfer.getData('url')e.dataTransfer.getData('text')来获取。
  • 自定义:通过设置draggable="true"属性来使元素变得可拖放。

拖放元素 3 个事件

  • dragstart:在按住鼠标键不放并开始移动鼠标的那一刻,被拖动元素上会触发 dragstart 事件。此时光标会变成非放置符号(圆环中间一条斜杠),表示元素不能放到自身上。
  • drag:dragstart 事件触发后,只要目标还被拖动就会持续触发 drag 事件。
  • dragend:当拖动停止时(把元素放到有效或无效的放置目标上),会触发 dragend 事件。
<div class="side">
  <div class="rect graphic" draggable="true">长方形</div>
  <div class="circle graphic" draggable="true">圆形</div>
</div>
<script>
  //拖放元素
  const graphics = document.querySelectorAll('.graphic');
  let graphic;
  //拖放元素-添加监听事件
  graphics.forEach((task) => {
    //拖放元素-dragstart事件
    task.addEventListener('dragstart', (e) => {
      console.log('拖放元素 - dragstart');
      graphic = task;
      task.style.borderColor = 'red';
    });

    //拖放元素-drag事件
    task.addEventListener('drag', (e) => {
      // console.log('拖放元素 -drag事件');
      graphic.style.borderColor = 'yellow';
    });
    //拖放元素-dragend事件
    task.addEventListener('dragend', () => {
      console.log('拖放元素 - dragend事件');
      graphic.style.borderColor = '#ccc';
    });
  });
</script>

三、放置目标

元素默认是不允许放置的,我们拖动元素时,会看到一个特殊光标(圆环中间一条斜杠)表示不能放下。
不过我们可以通过覆盖 dragenterdragover 事件的默认行为,可以把任何元素转换为有效的放置目标。

let droptarget = document.getElementById('droptarget');
droptarget.addEventListener('dragover', (event) => {
  event.preventDefault();
});
droptarget.addEventListener('dragenter', (event) => {
  event.preventDefault();
});

执行上面的代码之后,把元素拖动到这个

上应该可以看到光标变成了允许放置的样子。另外,drop 事件也会触发。

droptarget.addEventListener('drop', (event) => {
  event.preventDefault();
});

放置目标是指被拖动的元素松开鼠标时被放置的目标;拖放元素被拖动到放置目标上时,将依次触发 dragenter、dragover 和 dragleave 或 drop 这四个事件,我们来看一个例子

  • dragenter:当拖动元素进入放置目标的范围内时,会触发 dragenter 事件。
  • dragover:在拖动元素持续停留在放置目标元素上时,会持续触发 dragover 事件。
  • dragleave or drop:当拖动元素移出放置目标的范围时,会触发 dragleave 事件。当拖动元素在放置目标上释放时,会触发 drop 事件。2 者只能触发一个。
<div class="main" id="dropzone"></div>
<script>
  //拖放目标
  const dropzone = document.getElementById('dropzone');
  //拖放目标-dragenter事件
  dropzone.addEventListener('dragenter', (e) => {
    e.preventDefault();
    console.log('拖放目标 - dragenter事件');
    dropzone.style.backgroundColor = 'red';
  });
  //拖放目标-dragover事件
  dropzone.addEventListener('dragover', (e) => {
    e.preventDefault();
    // console.log('拖放目标-dragover事件');
    dropzone.style.backgroundColor = '#eee';
  });
  //拖放目标-dragleave事件
  dropzone.addEventListener('dragleave', () => {
    console.log('拖放目标 - dragleave事件');
    dropzone.style.backgroundColor = 'red';
  });
  //拖放目标-drop事件
  dropzone.addEventListener('drop', (e) => {
    e.preventDefault();
    console.log('拖放目标 - drop事件');
  });
</script>

四、dataTransfer 对象

除非数据受影响,否则简单的拖放并没有实际意义。为实现拖动操作中的数据传输,event 对象上暴露了 dataTransfer 对象,用于从被拖动元素向放置目标传递字符串数据。dataTransfer 对象有两个主要方法:getData()和 setData()。getData()用于获取 setData()存储的值。

// 传递文本
event.dataTransfer.setData('text', 'csdn');
let text = event.dataTransfer.getData('text');
// 传递 URL
event.dataTransfer.setData('URL', 'https://csdn.com/');
let url = event.dataTransfer.getData('URL');

在实际使用种,我们一般在 dragstart 事件中设置数据,在 drop 事件中获取获取,我们来看一个例子

  • dragstart 事件中手动调用 setData()存储自定义数据
  • drop 事件中手动调用 getData()获取自定义数据
<script>
    //拖放元素-dragstart事件
    task.addEventListener('dragstart', (e) => {
      console.log('拖放元素 - dragstart');
      data = {
        type: 'circle',
        style: {},
        position: {
          x: 0,
          y: 0,
        },
        animation: {},
        e: {
          onClick: null,
        },
      };
      e.dataTransfer.setData('graphicData', JSON.stringify(data));
    });

//拖放目标-drop事件
  dropzone.addEventListener('drop', (e) => {
    e.preventDefault();
    console.log('拖放目标 - drop事件');
    let _data=e.dataTransfer.getData('graphicData')
  });
</script>

五、总结

拖放就是将“拖放元素“ 拖到 ”放置目标”上面。没有数据传输的拖动是没有意义的。拖得时候有 3 个事件,在dragstart事件中定义数据,放得时候有 4 个事件,在drop事件中接受数据。

六、画布编辑器

用户可以在画布上自由拖动图形元素案例
在这里插入图片描述

<!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>
    <style>
      .container {
        box-sizing: border-box;
        margin: 0 auto;
        width: 800px;
        height: 300px;
        overflow: hidden;
      }
      .side {
        box-sizing: border-box;
        padding: 30px;
        float: left;
        width: 220px;
        height: 300px;
        border: 1px solid pink;
        display: flex;
        flex-wrap: wrap;
      }
      .graphic {
        margin-left: 30px;
        width: 60px;
        height: 60px;
        background-color: darkgreen;
        display: flex;
        justify-content: center;
        align-items: center;
        border: 2px solid #ccc;
        color: #fff;
        text-align: center;
      }
      .rect {
        background-color: darkgreen;
      }
      .circle {
        border-radius: 50%;
        background-color: aqua;
      }
      .main {
        box-sizing: border-box;
        position: relative;
        float: left;
        margin-left: 20px;
        width: 560px;
        height: 300px;
        border: 1px solid lightblue;
      }
      #circle2 {
        position: absolute;
        display: none;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="side">
        <div class="rect graphic" draggable="true">长方形</div>
        <div class="circle graphic" draggable="true">圆形</div>
      </div>
      <div class="main" id="dropzone">
        <div id="circle2" class="circle graphic" draggable="true">圆形</div>
      </div>
    </div>
    <script>
      //拖放元素
      const graphics = document.querySelectorAll('.graphic');
      let graphic, data;

      //拖放元素-添加监听事件
      graphics.forEach((task) => {
        //拖放元素-dragstart事件
        task.addEventListener('dragstart', (e) => {
          console.log('拖放元素 - dragstart');
          graphic = task;
          //点击不同元素,获取不同的值
          data = {
            type: 'circle',
            style: {},
            position: {
              x: 0,
              y: 0,
            },
            animation: {},
            e: {
              onClick: null,
            },
          };
          e.dataTransfer.setData('graphicData', JSON.stringify(data));
          task.style.borderColor = 'red';
        });

        //拖放元素-drop事件
        task.addEventListener('drag', (e) => {
          // console.log('拖放元素 -drag事件');
          graphic.style.borderColor = 'yellow';
        });
        //拖放元素-dragend事件
        task.addEventListener('dragend', () => {
          console.log('拖放元素 - dragend事件');
          graphic.style.borderColor = '#ccc';
        });
      });

      //拖放目标
      const dropzone = document.getElementById('dropzone');
      //拖放目标-dragenter事件
      dropzone.addEventListener('dragenter', (e) => {
        console.log('拖放目标 - dragenter事件');
        e.preventDefault();
        dropzone.style.backgroundColor = 'red';
      });
      //拖放目标-dragover事件
      dropzone.addEventListener('dragover', (e) => {
        // console.log('拖放目标-dragover事件');
        e.preventDefault();
        dropzone.style.backgroundColor = '#eee';
      });
      //拖放目标-dragleave事件
      dropzone.addEventListener('dragleave', () => {
        console.log('拖放目标 - dragleave事件');
        dropzone.style.backgroundColor = 'red';
      });
      //拖放目标-drop事件
      dropzone.addEventListener('drop', (e) => {
        e.preventDefault();
        console.log('拖放目标 - drop事件');
        //获取到值之后
        console.log(e.dataTransfer.getData('graphicData').toString());
        const circle2 = document.getElementById('circle2');

        //根据定位画图
        circle2.style.display = 'flex';

        const rectInfo = dropzone.getBoundingClientRect();
        let top = e.clientY - rectInfo.y;
        let left = e.clientX - rectInfo.x;
        circle2.style.margin = 0;
        circle2.style.left = left + 'px';
        circle2.style.top = top + 'px';
      });
    </script>
  </body>
</html>

点个关注:谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值