html5 table的表头拖动,可拖动table表头的实现

前言

自己做的项目碰到这样一个需求,就是对所有的表格添加表头可以拖动的效果。我一想,这不简单,分分钟钟给你做出来。拿起我的电脑,啪啪啪就敲起来了。

bV3IQT?w=720&h=720

一定是哪里不对,以我的聪明才智,结果应该不是这样的,然后净下心来,好好理了下思路后,总算是做出来了。

至于结果嘛,我肯定是做出来的,像下面这种:

bV3Iqz?w=320&h=75

准备

首先要说明的是,我们的项目使用的表格大概只分为两类,一类是表头不固定,就是普通的表格,另一类是表头固定,tbody部分是可以滚动的。需要说明的是,表头固定的那种是需要用两个table去实现,做过的人应该也都明白。前者看起来比较简单,因为宽度是受thead里的th影响的,后者看起来就不好处理,因为你用两个table就会出现下面的情况:

bV3I2m?w=1926&h=288

emmm,这和我们想象的应该不一样,这可咋整,感觉处理起来很麻烦啊。想起看过element-ui中的表格,似乎有拖动表头的实现,先打开控制台看下结构吧:

bV3I5G?w=1482&h=894

呃,话说长这么大我都没用过

和这两个标签,但仔细观察上面有个width,看到这大概也知道是怎么回事了,打开MDN看下相关属性的描述,和想的一样,width能控制当前列的宽度。

宽度的控制我们是解决了,还有一个问题,就是拖动后,其他列的宽度改怎么改变,如下:

a

b

c

d

如果我拖动a列,改变的宽度应该怎样分配到b,c,d上,我这里是这样处理的,b、c、d有个属性去表示该列是否已经被拖动过了,如果b、c、d都没拖动过,那么把a改变的宽度平分到b、c、d三列的宽度上,如果b、c、d都改变了话,那么只改变最后一列d的宽度。好了,思路已经有了,我们可以去实现了。

事实证明,如果按照上面的设计就太蠢了,已经改成只改变拖动列后面的列且这些列没有改变过宽度。

实现

首先html结构大概是这样的:

ab

12

js方面

constructor (id, options) {

this._el = document.querySelector(`#${id}`);

// 实际使用中需要对dom结构进行判断,这里就不做了

this._tables = Array.from(this._el.querySelectorAll('table'));

setTimeout(() => this._resolveDom());

this.store = {

dragging: false, //是否拖动

draggingColumn: null, //拖动的对象

miniWidth: 30, //拖动的最小宽度

startMouseLeft: undefined, //鼠标点击时的clientX

startLeft: undefined, //th右离table的距离

startColumnLeft: undefined, //th左离table的距离

tableLeft: undefined, //table离页面左边的距离,

HColumns: [],

BColumns: [],

};

};

添加dom:

const [ THeader ] = this._tables;

let TBody;

const Tr = THeader.tHead.rows[0];

const columns = Array.from(Tr.cells);

const Bcolgroup = document.createElement('colgroup');

const cols = columns.map((item, index) => {

const col = document.createElement('col');

item.dataset.index = index;

col.width = +item.offsetWidth;

return col;

});

cols.reduce((newDom, item) => {

newDom.appendChild(item);

return newDom;

}, Bcolgroup);

const HColgroup = Bcolgroup.cloneNode(true);

THeader.appendChild(HColgroup);

//不管是一个table还是两个,都把header和body提出来

if (this._tables.length === 1) {

const [ , tbody ] = Array.from(THeader.children);

tbody.remove();

TBody = THeader.cloneNode();

TBody.appendChild(Bcolgroup);

TBody.appendChild(tbody);

this._el.appendChild(TBody);

} else {

[ , TBody ] = this._tables;

TBody.appendChild(Bcolgroup);

}

//拖动时的占位线

const hold = document.createElement('div');

hold.classList.add('resizable-hold');

this._el.appendChild(hold);

上面这块就是添加节点的,对dom进行处理,为了复用,这里我们不管你是表头固定还是表头不固定,我们都拆分为两个table,这样处理起来也方便的多。

然后就是处理手指移到列右侧cursor的值设为col-resize:

handleMouseMove(evt) {

//...

if (!this.store.dragging) {

const rect = target.getBoundingClientRect();

const bodyStyle = document.body.style;

if (rect.width > 12 && rect.right - event.pageX < 8) {

bodyStyle.cursor = 'col-resize';

target.style.cursor = 'col-resize';

this.store.draggingColumn = target;

} else {

bodyStyle.cursor = '';

target.style.cursor = 'pointer';

this.store.draggingColumn = null;

}

}

};

需要注意的是,getBoundingClientRect()获取的rigth是元素右侧距离页面左边缘的距离,不是离页面右边缘的距离。这里就是给thead的tr添加mousemove事件,当鼠标指针距离右边缘小于8的时候,改变指针形状,然后改变store里的状态,表示此时点击是可以拖动的了。

然后就是mousedown+mousemove+mouseup来处理拖动了:

const handleMouseDown = (evt) => {

if (this.store.draggingColumn) {

this.store.dragging = true;

let { target } = evt;

if (!target) return;

const tableEle = THeader;

const tableLeft = tableEle.getBoundingClientRect().left;

const columnRect = target.getBoundingClientRect();

const minLeft = columnRect.left - tableLeft + 30;

target.classList.add('noclick');

this.store.startMouseLeft = evt.clientX;

this.store.startLeft = columnRect.right - tableLeft;

this.store.startColumnLeft = columnRect.left - tableLeft;

this.store.tableLeft = tableLeft;

document.onselectstart = () => false;

document.ondragstart = () => false;

hold.style.display = 'block';

hold.style.left = this.store.startLeft + 'px';

const handleOnMouseMove = (event) => {

const deltaLeft = event.clientX - this.store.startMouseLeft;

const proxyLeft = this.store.startLeft + deltaLeft;

hold.style.left = Math.max(minLeft, proxyLeft) + 'px';

};

// 宽度是这样分配的,举个?,如果a,b,c,d,他们每个都有个changed状态,默认false,拖过a,a.changed改为true,改变的宽度就由剩下的b,c,d平摊,如果都改变了,就让最后一个元素d背锅

const handleOnMouseUp = (event) => {

if (this.store.dragging) {

const { startColumnLeft } = this.store;

const finalLeft = parseInt(hold.style.left, 10);

const columnWidth = finalLeft - startColumnLeft;

const index = +target.dataset.index;

HColgroup.children[index].width = columnWidth;

if (index !== this.store.HColumns.length - 1) {

this.store.HColumns[index].isChange = true;

}

const deltaLeft = event.clientX - this.store.startMouseLeft;

const changeColumns = this.store.HColumns.filter(v => !v.isChange && +v.el.width > 30);

changeColumns.forEach(item => {

item.el.width = +item.el.width - deltaLeft / changeColumns.length;

});

this.store.BColumns.forEach((item, i) => {

item.el.width = this.store.HColumns[i].el.width;

});

//...init store

}

document.removeEventListener('mousemove', handleOnMouseMove);

document.removeEventListener('mouseup', handleOnMouseUp);

document.onselectstart = null;

document.ondragstart = null;

// noclick主要是用来判断是点击还是拖动,防止拖动触发排序

setTimeout(() => {

target.classList.remove('noclick');

}, 0);

};

document.addEventListener('mouseup', handleOnMouseUp);

document.addEventListener('mousemove', handleOnMouseMove);

}

};

Tr.addEventListener('mousedown', handleMouseDown);

预览效果 (chrome + Safari + Firefox)

总结

觉得很有意思也很有用的东西,也让自己涨了很多姿势,源码,已经做成类的形式,使用起来还算简单,因为是突然提出的需求,还未做过多测试,可能存在不知道的bug。

祝福

写在最后,马上就要过年了,心情还是非常happy的。那么,我就在这里提前祝大家新年大吉、、吧,皮一下才开心,哎嘿嘿。拜拜~

bV3JjL?w=426&h=240

后续补充

更改了宽度改变的方式,应该是只改变拖动列后面的列的宽度。有BUG,colgroup放在了thead下面,导致在safari下面有BUG,已经修复了,看的不仔细,但上面的代码还没有改,看代码的化还是去看源码,我没发现这个问题,别人帮我找出来的。

emmmmm,又发现了一个问题,就是拖动最后一列时。。。我想想,先睡了==

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值