本身作的项目碰到这样一个需求,就是对全部的表格添加表头能够拖动的效果。我一想,这不简单,分分钟钟给你作出来。拿起个人电脑,啪啪啪就敲起来了。
必定是哪里不对,以个人聪明才智,结果应该不是这样的,而后净下心来,好好理了下思路后,总算是作出来了。
至于结果嘛,我确定是作出来的,像下面这种:html
首先要说明的是,咱们的项目使用的表格大概只分为两类,一类是表头不固定,就是普通的表格,另外一类是表头固定,tbody
部分是能够滚动的。须要说明的是,表头固定的那种是须要用两个table
去实现,作过的人应该也都明白。前者看起来比较简单,由于宽度是受thead
里的th
影响的,后者看起来就很差处理,由于你用两个table就会出现下面的状况:
emmm,这和咱们想象的应该不同,这可咋整,感受处理起来很麻烦啊。想起看过element-ui
中的表格,彷佛有拖动表头的实现,先打开控制台看下结构吧:
呃,话说长这么大我都没用过<colgroup>
和<col>
这两个标签,但仔细观察上面有个width
,看到这大概也知道是怎么回事了,打开MDN看下相关属性的描述,和想的同样,width
能控制当前列的宽度。
宽度的控制咱们是解决了,还有一个问题,就是拖动后,其余列的宽度改怎么改变,以下:git
a | b | c | d |
---|
若是我拖动a列,改变的宽度应该怎样分配到b,c,d上,我这里是这样处理的,b、c、d有个属性去表示该列是否已经被拖动过了,若是b、c、d都没拖动过,那么把a改变的宽度平分到b、c、d三列的宽度上,若是b、c、d都改变了话,那么只改变最后一列d的宽度。好了,思路已经有了,咱们能够去实现了。
事实证实,若是按照上面的设计就太蠢了,已经改为只改变拖动列后面的列且这些列没有改变过宽度。github
首先html结构大概是这样的:chrome
<table> <thead> <tr> <th>a<th> <th>b<th> </tr> </thead> <tbody> <tr> <th>1<th> <th>2<th> </tr> </tbody> </table>
js方面element-ui
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:app
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
:dom
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)ui
以为颇有意思也颇有用的东西,也让本身涨了不少姿式,源码,已经作成类的形式,使用起来还算简单,由于是忽然提出的需求,还未作过多测试,可能存在不知道的bug。this
写在最后,立刻就要过年了,心情仍是很是happy的。那么,我就在这里提早祝你们新年大吉、、吧,皮一下才开心,哎嘿嘿。拜拜~
更改了宽度改变的方式,应该是只改变拖动列后面的列的宽度。有BUG,colgroup
放在了thead
下面,致使在safari下面有BUG,已经修复了,看的不仔细,但上面的代码尚未改,看代码的化仍是去看源码,我没发现这个问题,别人帮我找出来的。emmmmm,又发现了一个问题,就是拖动最后一列时。。。我想一想,先睡了==