如何使用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
相关产品推荐
相关产品推荐

