越努力,越幸运,做个ccode~

0%

实现一个拖动改变列宽的table组件

前言

一直以来都很想实现一个拖拽改变列宽的 table 组件,这几天终于有机会去研究它。哈哈,笔者不才,没能自己实现,还是去网上找了个案例,研究了下。

乍一看好像挺简单的 其实就是原生 js 那些事,还是自己基础太差,想的太复杂了。

核心思想

在鼠标移动单元格右边距离右边距小于 10px 的时候 将鼠标改为col-resize样式, 如果当前单元格(哈哈哈,注意这个当前单元格 后面有坑)鼠标是按下的状态 那就鼠标移动多少距离 当前单元格就增加或减少多少宽度

大致代码如下

1
<!DOCTYPE html>
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 };

大功告成 ~