Layui table列宽拖拽需用MutationObserver监听th的style属性变更,因官方无resize事件;应在done回调中为每个th单独创建实例,用getComputedStyle读宽并防抖处理。
layui 官方 table 模块没有 resize 事件,也不存在「列宽拖拽完成」回调——你绑了 on('resize', ...) 或监听 table.on('resize'),它永远不会触发。
为什么 MutationObserver 是唯一靠谱方案
用户拖拽列边线时,Layui 实际只做一件事:直接修改对应 th 元素的 style.width(或 style.minWidth)。这个操作是同步、无节流、不封装的 DOM 写入。因此监听 style 属性变更就是最贴近底层的行为捕获方式。
- 不用依赖鼠标事件(
mousedown/mouseup)——分隔线是伪元素::after,无法绑定事件,且无法关联到具体哪一列 - 不推荐轮询
getComputedStyle——性能差、易漏判、难控制触发时机 - 不能只监听整个
thead——变动太泛,会收到大量无关 mutation(比如排序箭头 class 变更) - 必须在
done回调里为每个th单独创建MutationObserver实例,避免互相干扰
如何正确监听并读取真实宽度值
直接读 th.style.width 不可靠:Layui 有时写的是 min-width,有时带单位("120px"),有时为空字符串。应统一用 getComputedStyle(th).width 获取最终渲染宽度。
- 只观察
attributes类型变动,并过滤attributeName === 'style' - 加简单防抖(如
setTimeout延迟 50ms),防止连续拖拽触发多次处理 - 从
th上读取data-field或innerText,用于区分列上下文 - 示例中
console.log('列宽变为:', width)后,可立刻存入localStorage或发请求持久化
table.render({
elem: '#demo',
url: '/api/list',
done: function(res, curr, count) {
const ths = document.querySelectorAll('.layui-table th');
ths.forEach(th => {
const observer = new MutationObserver(mutations => {
mutations.forEach(m => {
if (m.type === 'attributes' && m.attributeName === 'style') {
const width = getComputedStyle(th).width;
const field = th.dataset.field;
console.log(`字段 ${field} 宽度变为:`, width);
// 存 localStorage: localStorage.setItem(`table-col-width-${field}`, width);
}
});
});
observer.observe(th, { attributes: true });
});
}
});
常见错误与兼容性注意点
这个方案在 Layui 2.8.x 至当前最新版(2024 年后发布)均有效,但容易在以下环节出错:
- 在
render外部或init阶段就尝试查询.layui-table th——此时 DOM 还未生成,querySelectorAll返回空集合 - 把
observer.observe()目标设成document.querySelector('.layui-table thead')——会收到海量冗余变更,CPU 占用飙升 - 没做防抖,拖拽过程中频繁触发
table.resize()或重绘逻辑,导致卡顿甚至浏览器崩溃 - 用
parseInt(th.style.width)解析,遇到th.style.minWidth = '150px'就拿不到值;而getComputedStyle能跨样式来源统一读取
真正麻烦的不是监听本身,而是后续动作:你拿到新宽度后,要不要同步更新 cols 配置?要不要持久化到 localStorage 并在下次初始化时注入?这些逻辑必须和 done 中的 observer 初始化解耦,否则 reload 时 observer 会重复绑定、内存泄漏。这是最容易被忽略的边界问题。


















