前言
一直以来都很想实现一个拖拽改变列宽的 table 组件,这几天终于有机会去研究它。哈哈,笔者不才,没能自己实现,还是去网上找了个案例,研究了下。
乍一看好像挺简单的 其实就是原生 js 那些事,还是自己基础太差,想的太复杂了。
核心思想
在鼠标移动单元格右边距离右边距小于 10px 的时候 将鼠标改为col-resize
样式, 如果当前单元格(哈哈哈,注意这个当前单元格 后面有坑)鼠标是按下的状态 那就鼠标移动多少距离 当前单元格就增加或减少多少宽度
大致代码如下
1 |
|
2 | <html> |
3 | <head> |
4 | <meta charset="utf-8" /> |
5 | <title>火星黑洞</title> |
6 | </head> |
7 | <body> |
8 | <table id="table" cellspacing="0" cellpadding="1" width="100%" border="1"> |
9 | <tbody> |
10 | <tr align="center" bgcolor="#1890ff"> |
11 | <td style="width:100px;">id</td> |
12 | <td>公司名称</td> |
13 | <td>姓名</td> |
14 | <td>岗位</td> |
15 | </tr> |
16 | <tr> |
17 | <td>20</td> |
18 | <td>科技有限公司</td> |
19 | <td>火星黑洞</td> |
20 | <td>web开发</td> |
21 | </tr> |
22 | </tbody> |
23 | </table> |
24 | <script type="text/javascript"> |
25 | var tTD; |
26 | var table = document.getElementById("table"); |
27 | for (i = 0; i < table.rows[0].cells.length; i++) { |
28 | table.rows[0].cells[i].onmousedown = function () { |
29 | console.log("onmounseDown"); |
30 | tTD = this; |
31 | if (event.offsetX > tTD.offsetWidth - 10) { |
32 | tTD.mouseDown = true; |
33 | tTD.oldX = event.x; |
34 | tTD.oldWidth = tTD.offsetWidth; |
35 | } |
36 | }; |
37 | table.rows[0].cells[i].onmouseup = function () { |
38 | if (tTD == undefined) tTD = this; |
39 | tTD.mouseDown = false; |
40 | tTD.style.cursor = "default"; |
41 | }; |
42 | table.rows[0].cells[i].onmousemove = function () { |
43 | console.log(event.offsetX, this.offsetWidth); |
44 | if (event.offsetX > this.offsetWidth - 10) |
45 | this.style.cursor = "col-resize"; |
46 | else this.style.cursor = "default"; |
47 | if (tTD == undefined) tTD = this; |
48 | console.log(tTD.mouseDown, "ttd.mouseDown"); |
49 | if (tTD.mouseDown != null && tTD.mouseDown == true) { |
50 | if (tTD.oldWidth + (event.x - tTD.oldX) > 0) |
51 | tTD.width = tTD.oldWidth + (event.x - tTD.oldX); // 现在的宽度 |
52 | tTD.style.width = tTD.width; |
53 | tTD.style.cursor = "col-resize"; |
54 | table = tTD; |
55 | while (table.tagName != "TABLE") table = table.parentElement; |
56 | for (j = 0; j < table.rows.length; j++) { |
57 | table.rows[j].cells[tTD.cellIndex].width = tTD.width; |
58 | } |
59 | } |
60 | }; |
61 | } |
62 | </script> |
63 | </body> |
64 | </html> |
vue 实现 table 组件
感觉自己可以了 很快上手 table 组件
给 th 绑定事件
1 | <thead> |
2 | <tr> |
3 | <th |
4 | v-for="head in thead" |
5 | @mousedown="mouseDown" |
6 | @mousemove="mouseMove" |
7 | @mouseup="mouseUp" |
8 | > |
9 | {{ head.prop ? head.prop : head }} |
10 | </th> |
11 | </tr> |
12 | </thead> |
实现相关事件
按照原生 js 那一套 我事先按照自己的理解将它的 tTD 都改为了 event.target, 想着 event.target 就是事件发生的元素
但是很遗憾,是可以实现拖动改变列宽效果,但是必须慢慢拖动,才可以改变列宽,一旦鼠标移速过快,该单元格宽度不会跟着鼠标变化, 鼠标会滑到下一个单元格上
百思不得其解 感觉逻辑没有问题 代码也几乎改成了一模一样 只有源码是tTD
我用的event.target
为什么源码写的表格拖拽起来是如此丝滑,鼠标到哪 单元格右边边框线跟到哪 而我的组件,就是跟不上我的鼠标
这到底是怎么回事呢?
又将源码中的 tTD 全部改为 event.target
bug 重现了,那重点还是在于它这个 tTD 的作用
于是又一行一行改,然后又打印对比 tTD 与 event.target 到底为什么不一样
当我在公司名称那一列快速往右移时, tTD 永远都是公司名称那一个单元格 event.targt
会变成 姓名那一单元格
恍然一下 想到了在写 canvase 画板的时候 画笔的速度跟不上鼠标 move 的速度 中间会出现空白
也就是说 鼠标移动太快的话 改变宽度的代码还来不及执行 而当前的 event.target 已经变成了下一列 所以我要改变的那一列宽度就不会改变
终于知道原因了,这也就是源码中定义了全局变量 tTD 的原因所在 tTD 并不是事件发生的当前元素 而是记录要改变宽度的那一单元格!!!
重新修改代码
1 | let th = undefined; |
2 | |
3 | const mouseDown = (e, xxx) => { |
4 | if (!border) return; |
5 | th = e.target; |
6 | |
7 | if (e.offsetX > th.offsetWidth - 10) { |
8 | th.down = true; |
9 | th.oldWidth = th.offsetWidth; |
10 | th.oldX = e.x; |
11 | } |
12 | }; |
13 | |
14 | const mouseMove = (e) => { |
15 | if (!border) return; |
16 | if (e.target.offsetWidth - e.offsetX < 10) { |
17 | e.target.style.cursor = "col-resize"; |
18 | } else { |
19 | e.target.style.cursor = "default"; |
20 | } |
21 | if (!th) th = e.target; |
22 | if (th.down) { |
23 | th.width = th.oldWidth + (e.x - th.oldX); |
24 | th.style.cursor = "col-resize"; |
25 | } |
26 | }; |
27 | |
28 | const mouseUp = (e) => { |
29 | if (!border) return; |
30 | if (!th) th = e.target; |
31 | th.down = false; |
32 | th.style.cursor = "defult"; |
33 | }; |
终于可以很顺畅的滑动了 单元格的宽度变化终于跟得上我的手速了~
给定初始列宽
但是我们在使用组件的时候 都会给每一列一个初始宽度 用 colgroup 统一管理列
1 | <table> |
2 | <colgroup> |
3 | <col v-for="head in thead" :width="head.width" /> |
4 | </colgroup> |
5 | ... |
6 | </table> |
使用组件
1 | <template> |
2 | <div> |
3 | <x-table border :thead="theadData" :data="tableData"></x-table> |
4 | </div> |
5 | </template> |
6 | |
7 | <script lang="ts"> |
8 | import { defineComponent, ref } from "vue"; |
9 | export default defineComponent({ |
10 | setup() { |
11 | const theadData = ref([ |
12 | { prop: "日期", width: 200 }, |
13 | { prop: "姓名", width: 200 }, |
14 | { prop: "地址" }, |
15 | ]); |
16 | const tableData = ref([ |
17 | [1, 2, 3], |
18 | [4, 5, 6], |
19 | [7, 8, 9], |
20 | [11, 12, 13], |
21 | ]); |
22 | return { tableData, theadData }; |
23 | }, |
24 | }); |
25 | </script> |
任意改变列宽
这样给定了初始宽度后 发现无法将列改变到很小的宽度 应该是初始宽度限制了列的最小宽度 我们应该改变 col 的宽度
vue3 v-for 中的 ref 数组 获取 col
1 | <colgroup> |
2 | <col |
3 | :ref="setColRef" |
4 | v-for="head in thead" |
5 | :width="head.width ?? head.width" |
6 | /> |
7 | </colgroup> |
组合式 API
1 | let colRefs = []; |
2 | const setColRef = (el) => { |
3 | if (el) { |
4 | colRefs.push(el); |
5 | } |
6 | }; |
最终代码 同样需要一个变量记录当前需要改变列宽的索引值 否则会引发同样的问题
1 | let th = undefined; |
2 | let currentIndex = undefined; |
3 | |
4 | let colRefs = []; |
5 | const setColRef = (el) => { |
6 | if (el) { |
7 | colRefs.push(el); |
8 | } |
9 | }; |
10 | |
11 | const mouseDown = (e, index) => { |
12 | if (!border) return; |
13 | th = e.target; |
14 | currentIndex = index; |
15 | |
16 | if (e.offsetX > th.offsetWidth - 10) { |
17 | th.down = true; |
18 | th.oldWidth = th.offsetWidth; |
19 | th.oldX = e.x; |
20 | } |
21 | }; |
22 | |
23 | const mouseMove = (e, index) => { |
24 | if (!border) return; |
25 | if (e.target.offsetWidth - e.offsetX < 10) { |
26 | e.target.style.cursor = "col-resize"; |
27 | } else { |
28 | e.target.style.cursor = "default"; |
29 | } |
30 | if (!th) { |
31 | th = e.target; |
32 | currentIndex = index; |
33 | } |
34 | if (th.down) { |
35 | // 改变当前索引 col 的宽度 |
36 | colRefs[currentIndex].width = th.oldWidth + (e.x - th.oldX); |
37 | th.style.cursor = "col-resize"; |
38 | } |
39 | }; |
40 | |
41 | const mouseUp = (e) => { |
42 | if (!border) return; |
43 | if (!th) { |
44 | th = e.target; |
45 | currentIndex = undefined; |
46 | } |
47 | th.down = false; |
48 | th.style.cursor = "defult"; |
49 | }; |
50 | return { setColRef, mouseDown, mouseMove, mouseUp }; |
大功告成 ~