表格的行与列的拖拽操作

2025-12-26 19:27:32

本文将介绍如何使用 js 实现表格的行与列的拖拽操作。效果如下:

行与列的拖拽互换按照惯例,我们先来实现一个简单的表格,这里为了书写方便就用 Vue 的 CDN 方式进行创建,HTML 代码如下。

表格的拖拽

{{item}}
{{ value[1] }}

表格# 实现行互换为了表格的行能够拖动起来,我们需要给表格的行添加 draggable 属性,并设置 dragstart、dragend 事件。若是对 HTML 中的 drag 等事件不清楚的可以再来回顾一下 MDN 的 drag_eventopen in new window。

先来梳理一下拖拽的流程:

拖拽开始时,记录拖拽行的 index拖拽结束时,记录拖拽结束时的 index拖拽结束时,互换拖拽行和结束行的位置然后是 Vue 中的自定义指令,我们通过自定义指令来绑定拖拽事件,并获取拖拽行的 index。

这里我们自定义 v-raw-drag 指令来绑定拖拽事件。

其中 tableDate 是整个表格的数据。

在自定义指令中,可以获取到被绑定的 DOM 元素和绑定的传参值。但是为了避免每行都独自传参,我们将所需要的数据都挂载到自定义指令上。

/** 拖拽行指令 */

const vRowDrag = {

mounted(el, bindings) {

// 获取所有行

const trs = el.getElementsByTagName('tbody')[0].getElementsByTagName('tr')

// 为避免每行都独自传参, 因此将行挂载到自定义指令上

vRowDrag.trs = trs // 所有行元素

vRowDrag.el = el // 整个表格元素

vRowDrag.data = bindings.value // 传参 tableData

initDirective() // 初始化各行

},

}

将所有值都绑定到自定义指令上后,我们就可以在 initDirective 函数中为每行绑定拖拽事件了。

/** 自定义指令初始化 */

function initDirective() {

// 依次给每一行添加拖拽事件

;[...vRowDrag.trs].forEach((tr) => createDraggableElement(tr))

bindEvent()

}

/** 创建拖拽事件 */

function createDraggableElement(tr) {

tr.draggable = true

// 设置拖拽事件

tr.addEventListener('dragstart', handleDragStart, false)

tr.addEventListener('dragend', handleDragEnd, false)

}

/** 处理绑定事件 */

function bindEvent() {

vRowDrag.el.addEventListener('dragover', handleDragOver, false)

vRowDrag.el.addEventListener('dragenter', (e) => e.preventDefault(), false)

// 去除默认行为

window.addEventListener('dragover', (e) => e.preventDefault(), false)

window.addEventListener('dragenter', (e) => e.preventDefault(), false)

}

在处理绑定事件的末尾,还添加了两个全局事件监听器:window.addEventListener。这两个事件监听器的作用是防止在拖拽过程中触发浏览器默认的拖拽行为,例如在拖拽元素到浏览器窗口边缘时自动滚动等行为。

然后开始编辑上述绑定事件中的 handleDragStart、handleDragEnd、handleDragOver 三个事件。这里的思路就很简单了,获取开始拖拽行,获取结束拖拽行,然后交换它们的位置。

function handleDragStart(e) {

// 获取拖拽行

const target = e.target

draggingIndex = [...vRowDrag.trs].findIndex((item) => item === target)

}

function handleDragOver(e) {

// 注意 这里的 e.target:表示触发事件的元素,即鼠标指针当前所在的元素 td,所以为了获取行要取它的父元素 parentNode。

const target = e.target.parentNode

overIndex = [...vRowDrag.trs].findIndex((item) => item === target)

}

function handleDragEnd(e) {

if (overIndex !== -1) {

// 互换行

const draggingData = vRowDrag.data[draggingIndex]

vRowDrag.data[draggingIndex] = vRowDrag.data[overIndex]

vRowDrag.data[overIndex] = draggingData

}

}

下面是完整的代码:

表格的拖拽

{{item}}
{{ value[1] }}

# 列拖拽有了行拖拽的基础,列拖拽就很简单了。

首先,我们还是需要一个自定义指令来绑定拖拽事件,当然也可以写在一起,我这里为了让代码更加清晰,就写了两份。

这里自定义指令为 v-col-drag,但是传参同行略有差异,因为还有一个表格头,所以参数有俩个:titleData 和 tableData。

基本的思路一样,区别在于,在表格头互换后,要维护 tbody 中的数据顺序也要跟随变化。以及在 dragover 事件中获取的就是目标 td 元素,无需取父元素 tr 的值。

function handleColDragEnd(e) {

if (overColIndex !== -1) {

// 互换列

const draggingTitle = vColDrag.titleData[draggingColIndex]

vColDrag.titleData[draggingColIndex] = vColDrag.titleData[overColIndex]

vColDrag.titleData[overColIndex] = draggingTitle

// 列数据互换

vColDrag.data.forEach((item) => {

const draggingData = item[draggingColIndex]

item[draggingColIndex] = item[overColIndex]

item[overColIndex] = draggingData

})

}

overColIndex = -1

draggingColIndex = -1

}

function handleColOver(e) {

// 获取的就是 目标 td 头元素

const target = e.target

overColIndex = [...vColDrag.ths].findIndex((item) => item === target)

}

以下是完整代码:

表格的拖拽

{{item}}
{{ value[1] }}

整个代码,实际上并不复杂,但是实现起来还是需要一些思考的。