JS 实现拖拽元素的功能

JS 实现拖拽元素的功能

这篇笔记比较短,主要过一遍 draggable 的事件。

首先简单看一下 HTML 实现:

<!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>
      ul {
      
      
        list-style: none;
        border: 1px solid #333;
        padding: 0.5em;
        margin: 1em auto;
        max-width: 720px;
        width: 80%;
      }
      li {
      
      
        cursor: grab;
      }
      .card {
      
      
        border-radius: 10px;
        border: 1px solid #bbb;
        padding: 0.5em;
        margin: 0.8em;
        box-shadow: 1px 1px 0.5em #ccc;
      }
      .dragging {
      
      
        cursor: grabbing;
      }
      .droppable {
      
      
        background-color: #ffe0ec;
      }
    </style>
  </head>
  <body>
    <ul>
      <li id="p1" class="card" draggable="true">
        <h2>Title for p1</h2>
        <p>Paragraph body for p1</p>
      </li>
      <li id="p2" class="card" draggable="true">
        <h2>Title for p2</h2>
        <p>Paragraph body for p2</p>
      </li>
    </ul>
    <ul>
      <li id="p3" class="card" draggable="true">
        <h2>Title for p3</h2>
        <p>Paragraph body for p3</p>
      </li>
      <li id="p4" class="card" draggable="true">
        <h2>Title for p4</h2>
        <p>Paragraph body for p4</p>
      </li>
    </ul>
    <script src="./draggable.js"></script>
  </body>
</html>

效果如下:

在这里插入图片描述

这里简单的加了一点 CSS(list 和 card 的部分),其他抓取元素后会出现的悬浮效果全都是浏览器的实现,而实现抓取的功能也挺简单的,就是在想要抓取的元素上添加 draggable="true" 这一属性。

所以接下来要做的,就是绑定正确的事件,并且实现 draggable 中对应的事件。

首先修改一下 css:

<style>
  li {
      
      
    cursor: grab;
  }
</style>

这样鼠标在进入的时候就是抓取的样式,这样也提示用户当前元素可以被抓取:

在这里插入图片描述

随后是在 js 里面添加最初的设定,获取所有的 list item,并且绑定事件。绑定事件的部分会在后面一个个实现,所以现在先加一个 placeholder:

const listItems = document.querySelectorAll('li');

listItems.forEach((li) => {
    
    });

dragstart

这是一个单独的事件处理,所有的内容会被绑定到 dragstart 的事件下面:

// callback 可以拉出来单独做一个 function
li.addEventListener('dragstart', (e) => {
    
    
  // code here
});

dragstart 是开始抓取的这一部分,实现的功能包括:

  1. 更改鼠标光标现实正在抓取的状态

    这一部分依旧通过 js 实现,通过 style 这一属性改变 cursor 的状态

    在这里插入图片描述

    代码为:

    li.style.cursor = 'grabbing';
    

    或者通过 class 名称修改,这需要添加对应的 css:

    li.classList.add('dragging');
    
  2. 更新传输数据

    这里使用的是 DataTransfer 对象,根据 MDN 所说 DataTransfer 是在实现 drag 功能中,用来保存被拖拽的数据的一个对象。

    所以这里主要实现的有两个部分:

    1. 传输当前被选中的 li 的 id
    2. 限定 drag 的操作只有 move
    e.dataTransfer.setData('text/plain', li.id);
    e.dataTransfer.effectAllowed = 'move';
    

drop

drop 需要实现 4 个部分:dragenterdragover, dragleavedrop 。另外,drop 的这个操作也是作用于 ul 之上,而不是 li,这部分的基础代码为:

// drop
const lists = document.querySelectorAll('ul');

lists.forEach((list) => {
    
    
  // code here
});

dragenter & dragover

这里实现的功能主要是当被拖拽的部分进入到可被 drop 的地方,那么可被 drop 的部分应该会有一个颜色改变的提示。

list.addEventListener('dragenter', (e) => {
    
    
  // check only accept the correct type of data
  if (e.dataTransfer.types[0] === 'text/plain') {
    
    
    e.preventDefault();
    list.classList.add('droppable');
  }
});

这里加了一个 check e.dataTransfer.types[0] === 'text/plain',主要也是因为在真实的案例中,很可能会有抓取一些 html、DOM 结点的操作,这里想要确认被拖拽的只是字符串部分。

如果想要实现 drop 的功能,dragover 是个必须要调用 preventDefault 去阻止默认功能的实现:

list.addEventListener('dragover', (e) => {
    
    
  // prevent default to allow drop
  if (e.dataTransfer.types[0] === 'text/plain') {
    
    
    e.preventDefault();
  }
});

实现后效果如下:

在这里插入图片描述

dragleave

当 item 进入可被 drop 区域时添加的 class,同样也需要在 item 离开该区域时被移除,这一部分就可以在 dragleave 中实现:

list.addEventListener('dragleave', (e) => {
    
    
  list.classList.remove('droppable');
});

这时候实现完了会有一个小麻烦,那就是当被拖拽的对象进入另一个子结点的时候,它也算离开了当前结点:

在这里插入图片描述

这一部分的修改具体还是需要依赖实现去完成,这里主要通过寻找最近的 ul,并与当前的 ul 进行判断,如果不是同一个的话就会进行删除:

list.addEventListener('dragleave', (e) => {
    
    
  if (e.relatedTarget.closest('ul') !== list)
    list.classList.remove('droppable');
});

drop

drop 的部分就需要获取被拉动的 id,判断当前 list 是否包含对应 id:

  • 是的话停止继续执行
  • 否的话先删除当前元素,并且在对应的 list 中添加被删除的元素

实现如下:

list.addEventListener('drop', (e) => {
    
    
  const id = e.dataTransfer.getData('text/plain');
  const listArr = Array.from(list.children);
  if (listArr.find((li) => li.id === id)) return;

  const listItem = document.querySelector(`#${
      
      id}`);
  listItem.remove();
  list.appendChild(listItem);
  list.classList.remove('droppable');
});

效果如下:

在这里插入图片描述

完整 JS 代码

// drag
const listItems = document.querySelectorAll('li');

const connectDrag = (e, li) => {
    
    
  li.classList.add('dragging');
  e.dataTransfer.setData('text/plain', li.id);
  e.dataTransfer.effectAllowed = 'move';
};

listItems.forEach((li) =>
  li.addEventListener('dragstart', (e) => connectDrag(e, li))
);

// drop
const lists = document.querySelectorAll('ul');

lists.forEach((list) => {
    
    
  list.addEventListener('dragenter', (e) => {
    
    
    // check only accept the correct type of data
    if (e.dataTransfer.types[0] === 'text/plain') {
    
    
      e.preventDefault();
      list.classList.add('droppable');
    }
  });

  list.addEventListener('dragleave', (e) => {
    
    
    if (e.relatedTarget.closest('ul') !== list)
      list.classList.remove('droppable');
  });

  list.addEventListener('dragover', (e) => {
    
    
    // prevent default to allow drop
    if (e.dataTransfer.types[0] === 'text/plain') {
    
    
      e.preventDefault();
    }
  });

  list.addEventListener('drop', (e) => {
    
    
    const id = e.dataTransfer.getData('text/plain');
    const listArr = Array.from(list.children);
    if (listArr.find((li) => li.id === id)) return;

    const listItem = document.querySelector(`#${
      
      id}`);
    listItem.remove();
    list.appendChild(listItem);
    list.classList.remove('droppable');
  });
});

reference

猜你喜欢

转载自blog.csdn.net/weixin_42938619/article/details/130789243