You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用JavaScript拖拽API实现网格行列排序

使用JavaScript拖拽API实现网格行列重排

现有网格结构

CSS代码

.grid {
  display: grid;
  grid-template-columns: 100px 100px 100px 100px 100px;
  grid-template-rows: 50px 50px 50px 50px;
  grid-gap: 10px;
  &>* {
    padding: 10px;
    border: 1px solid black;
  }
  &>div:first-child {
    border: none;
  }
  & .column-header {
    background-color: lightgray;
  }
  & .row-header {
    background-color: lightgray;
  }
}

HTML代码

<div class="grid">
  <div></div>
  <div class="column-header">Col 1</div>
  <div class="column-header">Col 2</div>
  <div class="column-header">Col 3</div>
  <div class="column-header">Col 4</div>

  <div class="row-header">Row 1</div>
  <div>Cell 1.1</div>
  <div>Cell 2.1</div>
  <div>Cell 3.1</div>
  <div>Cell 4.1</div>

  <div class="row-header">Row 2</div>
  <div>Cell 1.2</div>
  <div>Cell 2.2</div>
  <div>Cell 3.2</div>
  <div>Cell 4.2</div>

  <div class="row-header">Row 3</div>
  <div>Cell 1.3</div>
  <div>Cell 2.3</div>
  <div>Cell 3.3</div>
  <div>Cell 4.3</div>

  <div class="row-header">Row 4</div>
  <div>Cell 1.4</div>
  <div>Cell 2.4</div>
  <div>Cell 3.4</div>
  <div>Cell 4.4</div>
</div>

拖拽重排实现方案

1. 列头横向重排

核心逻辑:标记列头可拖拽,记录拖拽列索引,通过drop事件调整所有行内对应列的单元格位置。

const grid = document.querySelector('.grid');
const columnHeaders = document.querySelectorAll('.column-header');

// 初始化列头拖拽属性与事件
columnHeaders.forEach(header => {
  header.draggable = true;
  
  header.addEventListener('dragstart', (e) => {
    const columnIndex = Array.from(grid.children).indexOf(e.target);
    e.dataTransfer.setData('text/plain', columnIndex);
    e.target.classList.add('dragging');
  });
  
  header.addEventListener('dragend', (e) => {
    e.target.classList.remove('dragging');
  });
});

// 处理列拖拽的放置逻辑
grid.addEventListener('dragover', (e) => {
  e.preventDefault();
  const draggingEl = document.querySelector('.dragging');
  if (!draggingEl?.classList.contains('column-header')) return;
  
  const colSiblings = Array.from(grid.children).filter(el => el.classList.contains('column-header'));
  const nextCol = getDragHorizonTarget(colSiblings, e.clientX);
  
  nextCol ? grid.insertBefore(draggingEl, nextCol) : grid.appendChild(draggingEl);
});

grid.addEventListener('drop', (e) => {
  e.preventDefault();
  const originalColIdx = parseInt(e.dataTransfer.getData('text/plain'));
  const newColIdx = Array.from(grid.children).indexOf(document.querySelector('.dragging'));
  
  // 按行拆分网格元素,调整每一行的列顺序
  const rows = Array.from(grid.children).reduce((acc, el, idx) => {
    idx % 5 === 0 && acc.push([]);
    acc[acc.length - 1].push(el);
    return acc;
  }, []);
  
  rows.forEach(row => {
    if (row.length < 5) return;
    const [movedCell] = row.splice(originalColIdx, 1);
    row.splice(newColIdx, 0, movedCell);
    row.forEach(cell => grid.appendChild(cell));
  });
});

// 辅助函数:计算横向拖拽的目标插入位置
function getDragHorizonTarget(siblings, x) {
  return siblings.reduce((closest, el) => {
    const rect = el.getBoundingClientRect();
    const offset = x - rect.left - rect.width / 2;
    return offset < 0 && offset > closest.offset ? { offset, element: el } : closest;
  }, { offset: Number.NEGATIVE_INFINITY }).element;
}

2. 行头纵向重排

核心逻辑:标记行头可拖拽,记录拖拽行索引,通过dragover事件直接移动整行的5个元素。

const rowHeaders = document.querySelectorAll('.row-header');

// 初始化行头拖拽属性与事件
rowHeaders.forEach(header => {
  header.draggable = true;
  
  header.addEventListener('dragstart', (e) => {
    const elIdx = Array.from(grid.children).indexOf(e.target);
    const rowIdx = Math.floor(elIdx / 5);
    e.dataTransfer.setData('text/plain', rowIdx);
    e.target.classList.add('dragging-row');
  });
  
  header.addEventListener('dragend', (e) => {
    e.target.classList.remove('dragging-row');
  });
});

// 处理行拖拽的放置逻辑
grid.addEventListener('dragover', (e) => {
  e.preventDefault();
  const draggingEl = document.querySelector('.dragging-row');
  if (!draggingEl?.classList.contains('row-header')) return;
  
  const rowSiblings = Array.from(grid.children).filter(el => el.classList.contains('row-header'));
  const nextRow = getDragVerticalTarget(rowSiblings, e.clientY);
  
  const dragRowStartIdx = Array.from(grid.children).indexOf(draggingEl);
  const dragRow = Array.from(grid.children).slice(dragRowStartIdx, dragRowStartIdx + 5);
  
  // 移除原行并插入到目标位置
  dragRow.forEach(cell => grid.removeChild(cell));
  if (nextRow) {
    const targetStartIdx = Array.from(grid.children).indexOf(nextRow);
    dragRow.forEach((cell, i) => grid.insertBefore(cell, grid.children[targetStartIdx + i]));
  } else {
    dragRow.forEach(cell => grid.appendChild(cell));
  }
});

grid.addEventListener('drop', (e) => {
  e.preventDefault();
  document.querySelector('.dragging-row')?.classList.remove('dragging-row');
});

// 辅助函数:计算纵向拖拽的目标插入位置
function getDragVerticalTarget(siblings, y) {
  return siblings.reduce((closest, el) => {
    const rect = el.getBoundingClientRect();
    const offset = y - rect.top - rect.height / 2;
    return offset < 0 && offset > closest.offset ? { offset, element: el } : closest;
  }, { offset: Number.NEGATIVE_INFINITY }).element;
}

注意事项

  • 两个拖拽逻辑可合并到同一JS文件,通过dragging和dragging-row类名区分状态
  • 辅助函数负责计算拖拽时的视觉插入位置,确保拖拽体验符合直觉
  • 列重排需遍历所有行调整单元格,行重排直接移动整行元素,逻辑更简洁

内容的提问来源于stack exchange,提问作者pistacchio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 22:04:56