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

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

本文介绍了如何实现元素的拖动功能,包括简单元素的拖动、列表项的拖动以及表格列和行的拖动。通过处理鼠标的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

推荐文章

Vue3中如何进行异步组件的加载?
2024-11-17 04:29:53 +0800 CST
html折叠登陆表单
2024-11-18 19:51:14 +0800 CST
Redis和Memcached有什么区别?
2024-11-18 17:57:13 +0800 CST
imap_open绕过exec禁用的脚本
2024-11-17 05:01:58 +0800 CST
平面设计常用尺寸
2024-11-19 02:20:22 +0800 CST
使用Ollama部署本地大模型
2024-11-19 10:00:55 +0800 CST
PyMySQL - Python中非常有用的库
2024-11-18 14:43:28 +0800 CST
基于Webman + Vue3中后台框架SaiAdmin
2024-11-19 09:47:53 +0800 CST
liunx服务器监控workerman进程守护
2024-11-18 13:28:44 +0800 CST
markdowns滚动事件
2024-11-19 10:07:32 +0800 CST
乐观锁和悲观锁,如何区分?
2024-11-19 09:36:53 +0800 CST
最全面的 `history` 命令指南
2024-11-18 21:32:45 +0800 CST
IP地址获取函数
2024-11-19 00:03:29 +0800 CST
内网穿透技术详解与工具对比
2025-04-01 22:12:02 +0800 CST
rangeSlider进度条滑块
2024-11-19 06:49:50 +0800 CST
Rust 中的所有权机制
2024-11-18 20:54:50 +0800 CST
一个数字时钟的HTML
2024-11-19 07:46:53 +0800 CST
介绍 Vue 3 中的新的 `emits` 选项
2024-11-17 04:45:50 +0800 CST
Node.js中接入微信支付
2024-11-19 06:28:31 +0800 CST
维护网站维护费一年多少钱?
2024-11-19 08:05:52 +0800 CST
程序员茄子在线接单