尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

jQuery表格列宽拖动:colResizable插件原理与实战踩坑记录

jQuery表格列宽拖动:colResizable插件原理与实战踩坑记录 简介面向网页前端开发者及需要优化表格交互的页面设计人员jQuery表格列宽拖动插件资源用于解决默认表格列宽固定、无法按需调整的痛点适用于数据分析、报表展示等需要灵活操作表格的页面。压缩包共4个文件包括核心JS插件、jQuery基础库、可直接运行的demo.html以及备份文件整体仅34KB轻量易用便于集成到现有项目。演示页与源码展示了通过参数控制列宽拖动效果的具体写法如fixed设置首列是否固定、liveDrag控制拖拽时实时反馈、padding调整边距并支持onResize回调以便在列宽变化后执行自定义逻辑开发者可根据项目需求自由组合这些参数。插件基于jQuery事件机制动态计算并更新表格样式兼容Chrome、Firefox、Safari、Edge等主流浏览器。目前已有272人学习下载获取后即可获得完整插件源码与演示环境无需额外搜索配置能快速为表格增加人性化的列宽拖拽交互提升用户浏览与操作数据的体验。 上个月接了个老项目的迭代后台一个数据表格列太多很多字段被挤压得完全没法看用户天天反馈“这列也太窄了吧”。我翻了下项目现状jQuery 3.x 后端模板渲染不想为了这一个功能重写前端也不想引一堆重型组件。最后用了 colResizable-1.3.min.js 这个古早但好使的 jquery 插件半天不到就把“表格宽度拖动”这个需求给上线了。这篇文章把它的原理、配置、以及我在真实项目里踩过的几个坑全部梳理一遍给接手 jQuery 时代系统的同学一个完整的参考。1. 老项目里加列宽拖动为什么我选 colResizable1.1 一个不起眼的小功能体验上的大加分数据表格这种东西平时没人觉得列宽调整是多重要的功能。但是一旦字段多起来问题就很现实有的列明明只要 80px 就能展示完有的列比如备注、地址需要 300px 以上才够看前端写死宽度永远不可能满足所有人。用户的需求其实特别朴素——能自己拖着调调的宽度下次打开还在。这个需求放在新项目里实现方案多得很但在老项目里很多约束就出现了。页面里已经有了 jQuery大部分逻辑都是基于 jQuery 写的我不能为了一个列宽拖动去引入 Vue 或者 React 那一套。而 colResizable 正好是纯粹的 jQuery 插件压缩后几 KB不依赖其他任何库对老系统来说是最低侵入的选择。我当时试过手写拖动逻辑但发现要考虑的细节比想象中多后文会讲到那些坑所以在时间紧的情况下直接选成熟插件才是合理的。1.2 colResizable 的底层原理改的不是 td是 col用这个插件之前我下意识以为它是靠遍历每个单元格设置宽度实现的。后来翻了压缩前的源码才发现它的思路要聪明得多。初始化时插件会读一次当前每一列的宽度然后给表格动态插入 colgroup 和 col 元素——一个 col 对应一列。拖动的时候它修改的是对应 col 元素的 width 属性而不是挨个改 td。为什么用 col 而不是 td性能是一方面更关键的是保持一致性。一个普通的 HTML 表格可能有很多行如果每拖动一次就要遍历所有行去改 td 宽度数据量大时页面会明显卡顿而 col 元素天然就是用来控制整列宽度的浏览器渲染时直接按列宽计算不需要额外的逻辑。至于拖动的手柄插件是在表格上方动态创建了一个覆盖层用绝对定位把手柄放在每一列的分界线上然后监听 mousedown、mousemove、mouseup 事件。mousedown 时记录鼠标起始位置mousemove 时算出移动的像素差 delta然后把当前列宽度加 delta、右侧相邻列宽度减 delta这样整张表的总宽度基本保持不变。正是这个“左加右减”的设计决定了你在使用它时必须保证表格至少有两列也决定了动态增删列的时候必须重新初始化不然列索引对不上拖动就会改错列。这些细节在后文会详细展开。2. 最小可运行示例从 0 到 1 复刻列宽拖动2.1 HTML 和依赖的准备先上一份完整的最简示例。依赖就两个文件jQuery 和 colResizable-1.3.min.js。注意 colResizable 要求 jQuery 1.8 以上我在 jQuery 3.x 下实测没有问题所以老项目不用担心版本冲突。!DOCTYPE html html langzh-CN head meta charsetUTF-8 titlecolResizable 列宽拖动示例/title script srchttps://cdn.jsdelivr.net/npm/jquery3.6.0/dist/jquery.min.js/script script srccolResizable-1.3.min.js/script style table { width: 100%; border-collapse: collapse; } th, td { border: 1px solid #ddd; padding: 6px 8px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } /style /head body table iddataTable thead tr th编号/th th姓名/th th部门/th th联系电话/th th备注/th /tr /thead tbody tr td001/td td张三/td td技术部/td td13800138000/td td长期驻场/td /tr tr td002/td td李四/td td运营部/td td13900139000/td td负责华东区/td /tr /tbody /table script $(function () { $(#dataTable).colResizable({ liveDrag: true }); }); /script /body /html这套代码跑起来表格每一列右侧就会出现一个可拖动的条鼠标移上去变成左右调整的样式拖到合适位置松手列宽就变了。如果你使用的是 IE 或者比较老的渲染环境拖动的手柄样式可能需要自己在 CSS 里补。有一点建议不要用纯数字或者非常窄的宽度去做测试因为插件默认有 minWidth 的限制太窄的列拖起来会让你误以为失效了具体下面配置部分会讲。2.2 初始化方式与选择器使用初始化很简单找到表格的 jQuery 对象调用 colResizable 方法即可。我强烈建议用 id 选择器而不是 class尤其是页面里可能同时存在多个表格的时候。class 选择器虽然也可以但初始化后插件会给每个表格生成唯一的手柄容器如果多个表格共用一个 class你后面想单独控制某个表格的配置或者销毁其中一个时selector 匹配范围过大容易误伤。初始化时机上如果是纯静态页面放在$(function(){})里就够了。如果是页面加载后才构建表格比如通过 AJAX 拿到数据后拼 HTML那必须在表格真实插入 DOM 之后再调用 colResizable不然插件读不到列宽初始化出来就是乱的。这也是一个高频踩坑点后文单独讲。3. 配置项详解这些参数决定最终体验colResizable 的配置项说多不多但每一个都不是摆设。我在项目里全部过了一遍挑出几个对最终体验影响最大的说明。3.1 liveDrag实时拖动还是松手生效liveDrag 这个参数默认是 false。意思是你拖动的时候列宽不会跟着变只有松开鼠标那一刻表格才会一次性调整宽度。默认值主要是为了照顾老 IE 和复杂表格的渲染性能避免拖动过程中频繁触发重排导致卡顿。但现代浏览器渲染能力普遍没问题所以我的建议是直接设成 true用户体验完全是两种级别。用户拖动时马上能看到列在跟着走和自己要的效果对比比等松手才看到结果要直观得多。如果你的表格特别大比如上百列、上千行的数据liveDrag 开着确实会有性能隐患。这种场景的折中方案是保持 liveDrag: false然后配合一个 loading 遮罩或者提前用 CSS 设置table-layout: fixed能显著减少重排的开销。3.2 cookieID 和 postbackSafe列宽的持久化机制用户拖完列宽刷新页面又变回原样那这个功能就废了一半。colResizable 支持把列宽存到 cookie 里依靠的是 cookieID 这个参数。$(#dataTable).colResizable({ liveDrag: true, cookieID: dataTableColWidth });设置了 cookieID 之后每次用户拖动结束宽度数组会被序列化成一个 JSON 字符串写入 cookie页面初始化时插件会自动读取 cookie 并恢复列宽。在纯 jQuery 项目里这个机制基本就够了。postbackSafe 这个参数则是为 ASP.NET WebForms 场景准备的。因为 WebForms 的 postback 会重新生成整个页面 DOM普通的 cookie 恢复机制在某些回发时序下可能失效。把 postbackSafe 设为 true 后插件会在每次回发前把列宽临时存到一个隐藏域或额外机制里回发完成后重新应用。如果你不是 WebForms 项目这个参数基本用不上我自己也只在维护老 asp.net 系统时才开启它。3.3 minWidth/maxWidth 和 onResize精细控制列宽范围minWidth 和 maxWidth 可以传数字也可以传函数。传数字表示所有列共用同一个最小/最大宽度传函数则可以对每一列单独设置。比如某列内容就是固定的几段短文本你完全可以把它的最大宽度限制住防止用户把它拉到不合理的尺寸。$(#dataTable).colResizable({ liveDrag: true, minWidth: 60, maxWidth: function (index) { // 第一列最多 120px其他列最多 500px return index 0 ? 120 : 500; }, onResize: function (event, $table) { var widths this.colWidths; console.log(当前各列宽度 widths.join(, )); } });onResize 回调里this 指向当前的插件实例通过 this.colWidths 可以拿到实时的列宽数组。这里有个小技巧如果你需要把用户调整后的列宽保存到后端数据库而不是 cookie在 onResize 里做一次 AJAX 提交即可注意加个节流或者只在拖完时保存否则 liveDrag 开着会触发非常多次回调。4. 五个真实踩坑记录从现象到根因这个插件本身很稳定但我实际用下来问题基本都出在和其他代码、样式的配合上。下面这五个坑基本覆盖了我这次项目实施中遇到的全部问题按排查的完整过程记录。4.1 表格在隐藏容器里初始化列宽全部错乱现象是这个功能刚接入时用户反馈“打开页面后表格宽度是乱的有的列宽得离谱有的列窄到看不见”。我排查了一阵才发现这个表格所在的面板页面加载时默认是折叠的用户在展开面板后才看到表格。而我的初始化代码写在了全局的$(function(){})里此时面板还是隐藏状态插件去读取各列宽度时所有列宽都是 0 或者异常值后续拖动逻辑自然全乱了。根因在于隐藏元素无法被正确测量宽度。解决办法有两种一种是等用户展开面板、容器可见后再调用 colResizable另一种是初始化时不依赖插件自行测量而是先给表格的 colgroup 或 th 设置一组明确的初始宽度插件初始化时会参考这些值。我最后选择的是后者因为代码侵入更小也更稳。4.2 动态增删列之后拖动改错列项目里有一个“列显示控制”功能用户可以根据需要勾选显示哪些列本质上是动态增删 td 和 th。加了这段逻辑之后列宽拖动就彻底乱了。比如我明明在拖第 3 列结果第 4 列的宽度在变。这个问题排查花了不少时间。看源码才发现colResizable 初始化时把列索引和手柄位置绑定死了动态 DOM 发生变化后col 元素的数量和位置跟手柄的映射关系就对不上了插件不会自动感知。解决方案是改完列之后先销毁再重新初始化插件提供了 destroy 方法正确的调用顺序是// 动态操作列之后 $(#dataTable).colResizable(destroy); // 重新构建表格列 renderTable(); // 再次初始化 $(#dataTable).colResizable({ liveDrag: true, cookieID: dataTableColWidth });destroy 之后如果不清空旧的 cookie重新初始化时会按旧宽度恢复导致列宽和当前列数不匹配所以动态场景下我一般会同时把 cookie 清掉。4.3 cookieID 重复导致两个表格宽度互相串台这个坑比较隐蔽。页面里本来有 A、B 两个表格分别调用了 colResizable但两个都没有指定 cookieID结果两个表格的列宽永远互相覆盖用户调整了 A 表格B 表格跟着变关掉页面再打开只有后初始化那个表格的宽度被保留。主要原因很明确插件默认的 cookieID 是同一个常量不指定的话所有表格共用一个 cookie 键。解决方式就是给每个表格指定不同的 cookieID这个其实在官方文档里写了但确实是最容易被忽略的一条。我把这段代码贴出来供参考$(#tableA).colResizable({ liveDrag: true, cookieID: colWidthA }); $(#tableB).colResizable({ liveDrag: true, cookieID: colWidthB });4.4 和 Bootstrap 表格样式打架表头表体对不齐项目中另一个页面用了 Bootstrap 框架表格结构是“固定表头 滚动表体”的双表格方案——表头用的一个 table表体用的另一个 table靠 colgroup 或者等宽列来对齐。colResizable 初始化在表头表格上之后拖动了表头列宽表体表格完全没反应表头和表体瞬间对不上。这个场景需要手动同步。colResizable 的 onResize 回调里能拿到新的宽度数组把数组应用到表体表格的 colgroup 上再做一次对齐。伪代码如下$(#headerTable).colResizable({ liveDrag: true, onResize: function (event, $table) { var widths this.colWidths; $(#bodyTable col).each(function (index) { $(this).width(widths[index]); }); } });其实更根本的思路是双表格方案本身就是一个技术妥协表头表体分离必然会遇到列宽同步问题用 colResizable 这种基于单表格的插件就需要额外处理。如果项目是从零开始我更推荐用单表格 scroll 容器的方案能避开很多同步问题。4.5 拖动时文本被选中体验很别扭最后一个问题不影响功能但影响体验。鼠标拖动列宽时如果正好划过表格里的一行文字文本会被选中松手后留下一个难看的蓝色选区。这还是因为插件监听的是文档级 mousemove没有在拖动过程中屏蔽浏览器默认的文本选择行为。处理办法是配合一个 CSS 规则在用户按住拖动手柄的瞬间给 body 加一个禁用 user-select 的 classmouseup 时再移除。colResizable 提供的 draggingClass 配置项可以往触发拖动的手柄元素上挂 class但它是挂手柄上的我测试下来要彻底覆盖文本选中还是得配合整体 CSS 处理.col-dragging, .col-dragging * { user-select: none !important; cursor: col-resize; }在初始化时加上draggingClass: col-dragging这样拖动时所有文本都不会被选中手感干净很多。5. 什么情况下该用什么情况下该换说实话colResizable 不是万能的也不是“最现代”的方案但在特定场景里它是投入产出比最高的选择。5.1 依然是 jQuery 项目直接用它是最优解如果你接手的系统还是 jQuery 时代的技术栈页面里全是$(document).ready、.ajax、.on(click)这种写法那 colResizable 就是最合适的一个方案。它几 KB 的体积不会给现有代码结构带来任何负担配置也简单不需要引入 Node 构建链路不需要 webpack、npm直接script引进来就能用。老系统最怕的就是大改这个插件可以在完全不改变原有数据渲染逻辑的前提下把列宽拖动的体验加上去。加上它有 cookie 持久化和 destroy 重初始化这种基础能力覆盖了我上面提到的大部分可维护性需求。我个人的结论是在老项目里这种“小而稳”的插件的性价比远高于自己手写一套。5.2 新项目里的其他路如果是全新的项目并且技术栈是 Vue 或者 React那我不会建议强行套 jQuery 插件。跨框架混用引入太多兼容成本尤其是框架重新渲染时插件的 DOM 操作很容易和虚拟 DOM 产生冲突。新项目里更合适的做法是直接用原生实现或者找对应框架生态里的表格列宽方案。但这里有个现实场景要注意新项目未必都用了框架。也有一些团队的新项目就是服务端模板渲染加上少量的原生 JS这时候为了一个列宽拖动去引入前端框架是杀鸡用牛刀。这种项目我反而是推荐 colResizable 的因为它能让你在几十分钟内就把功能做完而不是从零去写事件监听、边界计算和 cookie 持久化。至于原生方案可以拆解成三步监听表头列边界的 mousedown、计算鼠标位移并修改 colgroup 中对应 col 的宽度、mouseup 时把宽度数组存到 localStorage。看起来不复杂但真正实现时要处理用户选中文本、双击自适应、最小宽度限制、容器 resize 后的重新计算、多个表格并存时存储 key 隔离每一环都打磨到位工作量其实不小。我后来自己写过一个简化版本核心代码二百多行只覆盖了基础拖动还没算上持久化。所以如果你只需要列宽拖动这一个功能直接用现成插件不丢人反而是在控制风险。最后再分享一个我从这个项目里学到的习惯所有引入第三方 jQuery 插件之前先去翻一遍它的配置文档和已知 issue尤其关注 destroy 和重新初始化这种生命周期方法是否完善。colResizable 这些方法都齐全所以在老项目里很稳如果你遇到一个插件连销毁能力都没有那基本就只能一条路走到黑排查问题的成本会高很多。本文还有配套的精品资源点击获取
返回列表