编程 如何实现元素的拖动功能,包括简单元素的拖动、列表项的拖动以及表格列和行的拖动

2024-11-18 15:30:45 +0800 CST views 494

本文介绍了如何实现元素的拖动功能,包括简单元素的拖动、列表项的拖动以及表格列和行的拖动。通过处理鼠标的mousedown、mousemove和mouseup事件,结合动态DOM操作,读者可以掌握拖动效果的实现技巧,提升用户体验。文中提供了详细的HTML和JavaScript代码示例,帮助读者理解拖动功能的核心逻辑和实现方法。

元素拖动

刚开始,咱们循序渐进,先来实现一个最简单的功能:让一个元素变成可拖动元素。

布局与样式

<!DOCTYPE html>
<html>
<head>
  <title>元素拖动</title>
  <style>
    #drag {
      width: 100px;
      height: 100px;
      border: 1px solid #cbd5e0;
      display: flex;
      justify-content: center;
      align-items: center;
      cursor: move;
      user-select: none;
      position: absolute;
    }
  </style>
</head>
<body>
  <div id="drag">橙某人</div>
</body>
</html>

事件处理

要让一个元素可拖动,我们需要处理三个事件:

  1. mousedown:按下。
  2. mousemove:移动。
  3. mouseup:释放。

具体逻辑如下:

<script>
  document.addEventListener('DOMContentLoaded', () => {
    const drag = document.getElementById('drag');
    // 添加鼠标按下事件
    drag.addEventListener('mousedown', mouseDownHandler);
    // 记录鼠标坐标信息
    let x = 0;
    let y = 0;

    // 鼠标按下事件
    function mouseDownHandler(e) {
      // 记录鼠标初始位置
      x = e.clientX;
      y = e.clientY;
      // 添加鼠标移动与释放事件
      document.addEventListener('mousemove', mouseMoveHandler);
      document.addEventListener('mouseup', mouseUpHandler);
    }

    // 鼠标移动事件
    function mouseMoveHandler(e) {
      // 计算鼠标拖动的距离
      const dx = e.clientX - x;
      const dy = e.clientY - y;
      // 将拖动距离赋值给目标元素
      drag.style.top = `${drag.offsetTop + dy}px`;
      drag.style.left = `${drag.offsetLeft + dx}px`;
      // 不断记录鼠标上一个位置
      x = e.clientX;
      y = e.clientY;
    }

    // 鼠标释放事件
    function mouseUpHandler() {
      // 重置相关变量
      x = 0;
      y = 0;
      // 移除事件
      document.removeEventListener('mousemove', mouseMoveHandler);
      document.removeEventListener('mouseup', mouseUpHandler);
    }
  });
</script>

列表拖动

简单的整完,咱们开始上点强度?,这次要做的功能如下:

布局与样式

<!DOCTYPE html>
<html>
<head>
  <title>列表拖动</title>
  <style>
    .item {
      width: 200px;
      height: 60px;
      border: 1px solid #cbd5e0;
      display: flex;
      justify-content: center;
      align-items: center;
      cursor: move;
      user-select: none;
      margin: 10px 0;
      box-sizing: border-box;
    }
  </style>
</head>
<body>
  <div id="list">
    <div class="item">第一个元素</div>
    <div class="item">第二个元素</div>
    <div class="item">第三个元素</div>
    <div class="item">第四个元素</div>
    <div class="item">第五个元素</div>
  </div>
</body>
</html>

事件处理

接下来,让每个元素变成可拖动元素:

<script>
  document.addEventListener('DOMContentLoaded', () => {
    const list = document.getElementById('list');
    list.querySelectorAll('.item').forEach(item => {
      item.addEventListener('mousedown', mouseDownHandler);
    });

    let x = 0;
    let y = 0;
    let draggingElement;

    function mouseDownHandler(e) {
      draggingElement = e.target;
      const rect = draggingElement.getBoundingClientRect();
      x = e.clientX - rect.left;
      y = e.clientY - rect.top;
      document.addEventListener('mousemove', mouseMoveHandler);
      document.addEventListener('mouseup', mouseUpHandler);
    }

    function mouseMoveHandler(e) {
      const left = e.clientX - x;
      const top = e.clientY - y;
      draggingElement.style.position = 'absolute';
      draggingElement.style.top = `${top}px`;
      draggingElement.style.left = `${left}px`;
    }

    function mouseUpHandler() {
      draggingElement.style.removeProperty('top');
      draggingElement.style.removeProperty('left');
      draggingElement.style.removeProperty('position');
      x = 0;
      y = 0;
      draggingElement = null;
      document.removeEventListener('mousemove', mouseMoveHandler);
      document.removeEventListener('mouseup', mouseUpHandler);
    }
  });
</script>

效果如下:


表格拖动 - 列

接下来要做的是表格上的拖动功能,先看效果图:

动态创建列表

在表格中实现拖动,我们首先要将表格转换为一个可拖动的列表。

事件处理

具体实现如下:

<script>
document.addEventListener('DOMContentLoaded', () => {
  const table = document.getElementById('table');
  table.querySelectorAll('th').forEach(headerCell => {
    headerCell.classList.add('draggable');
    headerCell.addEventListener('mousedown', mouseDownHandler);
  });

  let draggingElement;
  let draggingColumnIndex;
  let x = 0;
  let y = 0;
  let isDraggingStarted = false;

  function mouseDownHandler(e) {
    draggingColumnIndex = [].slice.call(table.querySelectorAll('th')).indexOf(e.target);
    x = e.clientX - e.target.offsetLeft;
    y = e.clientY - e.target.offsetTop;
    document.addEventListener('mousemove', mouseMoveHandler);
    document.addEventListener('mouseup', mouseUpHandler);
  }

  function mouseMoveHandler(e) {
    if (!isDraggingStarted) {
      isDraggingStarted = true;
      createList();
    }
  }

  function createList() {
    const rect = table.getBoundingClientRect();
    list = document.createElement('div');
    list.classList.add('list');
    list.style.position = 'absolute';
    list.style.left = rect.left + 'px';
    list.style.top = rect.top + 'px';
    table.parentNode.insertBefore(list, table);
    table.style.visibility = 'hidden';

    const originalCells = [].slice.call(table.querySelectorAll('tbody td'));
    const originalHeaderCells = [].slice.call(table.querySelectorAll('th'));
    const numColumns = originalHeaderCells.length;

    originalHeaderCells.forEach((headerCell, headerIndex) => {
      const { width } = window.getComputedStyle(headerCell);
      const item = document.createElement('div');
      item.classList.add('draggable');
      const newTable = document.createElement('table');
      newTable.setAttribute('class', 'list__table');
      newTable.style.width = width;
      const th = headerCell.cloneNode(true);
      let newRow = document.createElement('tr');
      newRow.appendChild(th);
      newTable.appendChild(newRow);

      const cells = originalCells.filter((c, idx) => {
        return (idx - headerIndex) % numColumns === 0;
      });

      cells.forEach(cell => {
        const newCell = cell.cloneNode(true);
        newCell.style.width = width + 'px';
        newRow = document.createElement('tr');
        newRow.appendChild(newCell);
        newTable.appendChild(newRow);
      });

      item.appendChild(newTable);
      list.appendChild(item);
    });
  }

  function mouseUpHandler() {
    document.removeEventListener('mousemove', mouseMove

Handler);
    document.removeEventListener('mouseup', mouseUpHandler);
  }
});
</script>

表格拖动 - 行

既然讲了表格的列拖动,那么行的拖动肯定也不能落下啦。?

CSS 样式

.list {
  border-top: 1px solid #ccc;
}
.list__table th,
.list__table td {
  border: 1px solid #ccc;
  border-top: none;
  box-sizing: border-box;
  padding: 10px;
  text-align: center;
  color: red;
}

事件处理

具体实现如下:

<script>
document.addEventListener('DOMContentLoaded', () => {
  const table = document.getElementById('table');
  table.querySelectorAll('tr').forEach((row, index) => {
    if (index === 0) return;
    const firstCell = row.firstElementChild;
    firstCell.classList.add('draggable');
    firstCell.addEventListener('mousedown', mouseDownHandler);
  });

  let draggingColumnIndex;
  let x = 0;
  let y = 0;
  let isDraggingStarted = false;
  let list;
  let draggingElement;
  let placeholder;

  function mouseDownHandler(e) {
    const originalRow = e.target.parentNode;
    draggingRowIndex = [].slice.call(table.querySelectorAll('tr')).indexOf(originalRow);
    x = e.clientX;
    y = e.clientY;
    document.addEventListener('mousemove', mouseMoveHandler);
    document.addEventListener('mouseup', mouseUpHandler);
  }

  function mouseMoveHandler(e) {
    if (!isDraggingStarted) {
      isDraggingStarted = true;
      createList();
      draggingElement = [].slice.call(list.children)[draggingRowIndex];
      draggingElement.classList.add('dragging');
      placeholder = document.createElement('div');
      placeholder.classList.add('placeholder');
      draggingElement.parentNode.insertBefore(placeholder, draggingElement.nextSibling);
      placeholder.style.height = draggingElement.offsetHeight + 'px';
    }
    draggingElement.style.position = 'absolute';
    draggingElement.style.top = (draggingElement.offsetTop + e.clientY - y) + 'px';
    draggingElement.style.left = (draggingElement.offsetLeft + e.clientX - x) + 'px';
    x = e.clientX;
    y = e.clientY;
    const prevEle = draggingElement.previousElementSibling;
    const nextEle = placeholder.nextElementSibling;
    if (prevEle && prevEle.previousElementSibling && isAbove(draggingElement, prevEle)) {
      swap(placeholder, draggingElement);
      swap(placeholder, prevEle);
      return;
    }
    if (nextEle && isAbove(nextEle, draggingElement)) {
      swap(nextEle, placeholder);
      swap(nextEle, draggingElement);
    }
  }

  function createList() {
    const rect = table.getBoundingClientRect();
    const width = window.getComputedStyle(table).width;
    list = document.createElement('div');
    list.classList.add('list');
    list.style.position = 'absolute';
    list.style.left = rect.left + 'px';
    list.style.top = rect.top + 'px';
    table.parentNode.insertBefore(list, table);
    table.style.visibility = 'hidden';

    table.querySelectorAll('tr').forEach(row => {
      const item = document.createElement('div');
      item.classList.add('draggable');
      const newTable = document.createElement('table');
      newTable.setAttribute('class', 'list__table');
      newTable.style.width = width;
      const newRow = document.createElement('tr');
      const cells = [].slice.call(row.children);
      cells.forEach(cell => {
        const newCell = cell.cloneNode(true);
        newCell.style.width = window.getComputedStyle(cell).width;
        newRow.appendChild(newCell);
      });
      newTable.appendChild(newRow);
      item.appendChild(newTable);
      list.appendChild(item);
    });
  }

  function swap(nodeA, nodeB) {
    // ... 类似前面的代码逻辑
  }

  function isAbove(nodeA, nodeB) {
    const rectA = nodeA.getBoundingClientRect();
    const rectB = nodeB.getBoundingClientRect();
    const centerPointA = rectA.top + rectA.height / 2;
    const centerPointB = rectB.top + rectB.height / 2;
    return centerPointA < centerPointB;
  }

  function mouseUpHandler() {
    placeholder && placeholder.parentNode.removeChild(placeholder);
    draggingElement.classList.remove('dragging');
    draggingElement.style.removeProperty('top');
    draggingElement.style.removeProperty('left');
    draggingElement.style.removeProperty('position');
    const endRowIndex = [].slice.call(list.children).indexOf(draggingElement);
    isDraggingStarted = false;
    list.parentNode.removeChild(list);
    let rows = [].slice.call(table.querySelectorAll('tr'));
    if (draggingRowIndex > endRowIndex) {
       rows[endRowIndex].parentNode.insertBefore(
         rows[draggingRowIndex], 
         rows[endRowIndex]
       );
    }else{
      rows[endRowIndex].parentNode.insertBefore(
        rows[draggingRowIndex],
        rows[endRowIndex].nextSibling
      );
    }
    table.style.removeProperty('visibility');
    document.removeEventListener('mousemove', mouseMoveHandler);
    document.removeEventListener('mouseup', mouseUpHandler);
  }
});
</script>

结语

通过以上步骤,我们实现了表格的列拖动和行拖动的效果。掌握这些技巧,可以让你```markdown
在需要拖动表格内容时更灵活地进行操作,提升用户体验。

小结

实现拖动功能的核心在于事件处理动态DOM操作。通过对元素的mousedownmousemovemouseup事件的监听,我们可以精确地控制元素的拖动行为。结合getBoundingClientRect方法获取元素的位置和尺寸信息,以及insertBefore等DOM操作方法,可以灵活地实现元素的拖动和交换。

拖动实现的几点总结:

  1. 事件处理mousedown用于捕获鼠标按下事件,mousemove用于检测鼠标移动的距离,并更新元素的位置,mouseup用于检测鼠标释放并清理事件监听。

  2. 坐标计算:通过计算鼠标在拖动元素上的相对位置,以及鼠标的移动距离,更新元素的topleft属性,实现元素的拖动效果。

  3. 占位元素:在拖动列表和表格行列时,使用占位元素保持页面布局的稳定,避免元素拖动时导致其他元素位置变化。

  4. 动态创建列表:在表格列拖动的实现中,通过动态创建与表格列对应的列表项,实现灵活的列拖动效果。这样避免了直接操作复杂的表格DOM结构,而是通过操作列表来简化问题。

  5. 拖动方向判断:通过比较元素中心点的位置,判断拖动的方向,以便在拖动过程中正确地交换元素位置。

  6. 交换元素:无论是列表项还是表格列或行,交换元素位置的核心在于正确使用insertBefore方法,确保交换后的DOM结构保持一致性。

最终效果展示:

通过这些技术,我们可以实现更复杂的UI交互效果,如列表项拖动、表格列拖动、表格行拖动等。最终的实现效果不仅功能完善,还具有良好的用户体验。


完整源码

你可以通过Gitee仓库获取本文中所有示例的完整源码。

学习和练习

拖动效果的实现是前端开发中的一个经典案例,也是很多高级UI交互中的核心部分。通过对这类功能的实现和理解,能够帮助你更好地掌握DOM操作、事件处理等前端核心技术。

复制全文 生成海报 前端开发 用户交互 JavaScript DOM操作 CSS

推荐文章

GROMACS:一个美轮美奂的C++库
2024-11-18 19:43:29 +0800 CST
快手小程序商城系统
2024-11-25 13:39:46 +0800 CST
Golang 中应该知道的 defer 知识
2024-11-18 13:18:56 +0800 CST
20个超实用的CSS动画库
2024-11-18 07:23:12 +0800 CST
MyLib5,一个Python中非常有用的库
2024-11-18 12:50:13 +0800 CST
JavaScript设计模式:单例模式
2024-11-18 10:57:41 +0800 CST
PHP解决XSS攻击
2024-11-19 02:17:37 +0800 CST
js一键生成随机颜色:randomColor
2024-11-18 10:13:44 +0800 CST
java MySQL如何获取唯一订单编号?
2024-11-18 18:51:44 +0800 CST
Go 中的单例模式
2024-11-17 21:23:29 +0800 CST
Go语言中的mysql数据库操作指南
2024-11-19 03:00:22 +0800 CST
解决python “No module named pip”
2024-11-18 11:49:18 +0800 CST
如何在 Linux 系统上安装字体
2025-02-27 09:23:03 +0800 CST
Vue3中的响应式原理是什么?
2024-11-19 09:43:12 +0800 CST
JS中 `sleep` 方法的实现
2024-11-19 08:10:32 +0800 CST
windows下mysql使用source导入数据
2024-11-17 05:03:50 +0800 CST
2024年微信小程序开发价格概览
2024-11-19 06:40:52 +0800 CST
mysql删除重复数据
2024-11-19 03:19:52 +0800 CST
Python 基于 SSE 实现流式模式
2025-02-16 17:21:01 +0800 CST
PHP如何进行MySQL数据备份?
2024-11-18 20:40:25 +0800 CST
基于Flask实现后台权限管理系统
2024-11-19 09:53:09 +0800 CST
Hypothesis是一个强大的Python测试库
2024-11-19 04:31:30 +0800 CST
程序员茄子在线接单