前言
一直以来都很想实现一个拖拽改变列宽的 table 组件,这几天终于有机会去研究它。哈哈,笔者不才,没能自己实现,还是去网上找了个案例,研究了下。
乍一看好像挺简单的 其实就是原生 js 那些事,还是自己基础太差,想的太复杂了。
核心思想
在鼠标移动单元格右边距离右边距小于 10px 的时候 将鼠标改为col-resize样式, 如果当前单元格(哈哈哈,注意这个当前单元格 后面有坑)鼠标是按下的状态 那就鼠标移动多少距离 当前单元格就增加或减少多少宽度
大致代码如下
<!DOCTYPE html><html> <head> <meta charset="utf-8" /> <title>火星黑洞</title> </head> <body> <table id="table" cellspacing="0" cellpadding="1" width="100%" border="1"> <tbody> <tr align="center" bgcolor="#1890ff"> <td style="width:100px;">id</td> <td>公司名称</td> <td>姓名</td> <td>岗位</td> </tr> <tr> <td>20</td> <td>科技有限公司</td> <td>火星黑洞</td> <td>web开发</td> </tr> </tbody> </table> <script type="text/javascript"> var tTD; var table = document.getElementById("table"); for (i = 0; i < table.rows[0].cells.length; i++) { table.rows[0].cells[i].onmousedown = function () { console.log("onmounseDown"); tTD = this; if (event.offsetX > tTD.offsetWidth - 10) { tTD.mouseDown = true; tTD.oldX = event.x; tTD.oldWidth = tTD.offsetWidth; } }; table.rows[0].cells[i].onmouseup = function () { if (tTD == undefined) tTD = this; tTD.mouseDown = false; tTD.style.cursor = "default"; }; table.rows[0].cells[i].onmousemove = function () { console.log(event.offsetX, this.offsetWidth); if (event.offsetX > this.offsetWidth - 10) this.style.cursor = "col-resize"; else this.style.cursor = "default"; if (tTD == undefined) tTD = this; console.log(tTD.mouseDown, "ttd.mouseDown"); if (tTD.mouseDown != null && tTD.mouseDown == true) { if (tTD.oldWidth + (event.x - tTD.oldX) > 0) tTD.width = tTD.oldWidth + (event.x - tTD.oldX); // 现在的宽度 tTD.style.width = tTD.width; tTD.style.cursor = "col-resize"; table = tTD; while (table.tagName != "TABLE") table = table.parentElement; for (j = 0; j < table.rows.length; j++) { table.rows[j].cells[tTD.cellIndex].width = tTD.width; } } }; } </script> </body></html>vue 实现 table 组件
感觉自己可以了 很快上手 table 组件
给 th 绑定事件
<thead> <tr> <th v-for="head in thead" @mousedown="mouseDown" @mousemove="mouseMove" @mouseup="mouseUp" > {{ head.prop ? head.prop : head }} </th> </tr></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 并不是事件发生的当前元素 而是记录要改变宽度的那一单元格!!!
重新修改代码
let th = undefined;
const mouseDown = (e, xxx) => { if (!border) return; th = e.target;
if (e.offsetX > th.offsetWidth - 10) { th.down = true; th.oldWidth = th.offsetWidth; th.oldX = e.x; }};
const mouseMove = (e) => { if (!border) return; if (e.target.offsetWidth - e.offsetX < 10) { e.target.style.cursor = "col-resize"; } else { e.target.style.cursor = "default"; } if (!th) th = e.target; if (th.down) { th.width = th.oldWidth + (e.x - th.oldX); th.style.cursor = "col-resize"; }};
const mouseUp = (e) => { if (!border) return; if (!th) th = e.target; th.down = false; th.style.cursor = "defult";};终于可以很顺畅的滑动了 单元格的宽度变化终于跟得上我的手速了~~~~~~~
给定初始列宽
但是我们在使用组件的时候 都会给每一列一个初始宽度 用 colgroup 统一管理列
<table> <colgroup> <col v-for="head in thead" :width="head.width" /> </colgroup> ...</table>使用组件
<template> <div> <x-table border :thead="theadData" :data="tableData"></x-table> </div></template>
<script lang="ts"> import { defineComponent, ref } from "vue"; export default defineComponent({ setup() { const theadData = ref([ { prop: "日期", width: 200 }, { prop: "姓名", width: 200 }, { prop: "地址" }, ]); const tableData = ref([ [1, 2, 3], [4, 5, 6], [7, 8, 9], [11, 12, 13], ]); return { tableData, theadData }; }, });</script>任意改变列宽
这样给定了初始宽度后 发现无法将列改变到很小的宽度 应该是初始宽度限制了列的最小宽度 我们应该改变 col 的宽度
vue3 v-for 中的 ref 数组 获取 col
<colgroup> <col :ref="setColRef" v-for="head in thead" :width="head.width ?? head.width" /></colgroup>组合式 API
let colRefs = [];const setColRef = (el) => { if (el) { colRefs.push(el); }};最终代码 同样需要一个变量记录当前需要改变列宽的索引值 否则会引发同样的问题
let th = undefined;let currentIndex = undefined;
let colRefs = [];const setColRef = (el) => { if (el) { colRefs.push(el); }};
const mouseDown = (e, index) => { if (!border) return; th = e.target; currentIndex = index;
if (e.offsetX > th.offsetWidth - 10) { th.down = true; th.oldWidth = th.offsetWidth; th.oldX = e.x; }};
const mouseMove = (e, index) => { if (!border) return; if (e.target.offsetWidth - e.offsetX < 10) { e.target.style.cursor = "col-resize"; } else { e.target.style.cursor = "default"; } if (!th) { th = e.target; currentIndex = index; } if (th.down) { // 改变当前索引 col 的宽度 colRefs[currentIndex].width = th.oldWidth + (e.x - th.oldX); th.style.cursor = "col-resize"; }};
const mouseUp = (e) => { if (!border) return; if (!th) { th = e.target; currentIndex = undefined; } th.down = false; th.style.cursor = "defult";};return { setColRef, mouseDown, mouseMove, mouseUp };大功告成 ~~~~~~~