摘要:前言自己做的项目碰到这样一个需求,就是对所有的表格添加表头可以拖动的效果。需要说明的是,表头固定的那种是需要用两个去实现,做过的人应该也都明白。拜拜后续补充更改了宽度改变的方式,应该是只改变拖动列后面的列的宽度。
前言
自己做的项目碰到这样一个需求,就是对所有的表格添加表头可以拖动的效果。我一想,这不简单,分分钟钟给你做出来。拿起我的电脑,啪啪啪就敲起来了。
一定是哪里不对,以我的聪明才智,结果应该不是这样的,然后净下心来,好好理了下思路后,总算是做出来了。
至于结果嘛,我肯定是做出来的,像下面这种:
首先要说明的是,我们的项目使用的表格大概只分为两类,一类是表头不固定,就是普通的表格,另一类是表头固定,tbody部分是可以滚动的。需要说明的是,表头固定的那种是需要用两个table去实现,做过的人应该也都明白。前者看起来比较简单,因为宽度是受thead里的th影响的,后者看起来就不好处理,因为你用两个table就会出现下面的情况:
emmm,这和我们想象的应该不一样,这可咋整,感觉处理起来很麻烦啊。想起看过element-ui中的表格,似乎有拖动表头的实现,先打开控制台看下结构吧:
呃,话说长这么大我都没用过
宽度的控制我们是解决了,还有一个问题,就是拖动后,其他列的宽度改怎么改变,如下:
a | b | c | d |
---|
如果我拖动a列,改变的宽度应该怎样分配到b,c,d上,我这里是这样处理的,b、c、d有个属性去表示该列是否已经被拖动过了,如果b、c、d都没拖动过,那么把a改变的宽度平分到b、c、d三列的宽度上,如果b、c、d都改变了话,那么只改变最后一列d的宽度。好了,思路已经有了,我们可以去实现了。
事实证明,如果按照上面的设计就太蠢了,已经改成只改变拖动列后面的列且这些列没有改变过宽度。
首先html结构大概是这样的:
a | b | ||
---|---|---|---|
1 | 2 |
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的。那么,我就在这里提前祝大家新年大吉、、吧,皮一下才开心,哎嘿嘿。拜拜~
更改了宽度改变的方式,应该是只改变拖动列后面的列的宽度。有BUG,colgroup放在了thead下面,导致在safari下面有BUG,已经修复了,看的不仔细,但上面的代码还没有改,看代码的化还是去看源码,我没发现这个问题,别人帮我找出来的。
emmmmm,又发现了一个问题,就是拖动最后一列时。。。我想想,先睡了==
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/107383.html
摘要:前言自己做的项目碰到这样一个需求,就是对所有的表格添加表头可以拖动的效果。需要说明的是,表头固定的那种是需要用两个去实现,做过的人应该也都明白。拜拜后续补充更改了宽度改变的方式,应该是只改变拖动列后面的列的宽度。 前言 自己做的项目碰到这样一个需求,就是对所有的表格添加表头可以拖动的效果。我一想,这不简单,分分钟钟给你做出来。拿起我的电脑,啪啪啪就敲起来了。showImg(https:...
摘要:今天我就来讲讲插件的使用,它是如何实现列表表头自定义显示字段的,我把我的经验分享出来,满足一下不懂英语的人,给你们搭个快车。需求分析实现列表表头自定义显示字段,自定义表头排序。 序言 Yii2框架的扩展性能真的很不错,很多效果都可以通过插件去实现,你想不到的老外都帮你想好了,于是,人群中就流传了这么一句话:效果不会写不要紧,会用插件也不错。GitHub是一个庞大而且开放的资源库,平时有...
摘要:懒加载方式常见的有淘宝一屏用元素占据一定的高度,然后再去拉图片数据。但这种方式还是需要元素占位,淘宝一页的数据量其实不算大,因为它结合了分页。 背景 showImg(https://segmentfault.com/img/bVbhSVh?w=1606&h=440);大数据项目根据用户输入代码查询数据,用户的代码不可控(比如select from db limit 5000),有可能...
摘要:将设置为将和的放入一个的中,设置横向轴可以滚动,轴不可滚动。表头和表内容的横向方向滚动能力其实是父级样式赋予的。 1.为了固定表头我们需要先把表格的head和tbody切分到两个table里: 和 里。 2.将table-body放在一个table-body-box的div里设置y轴方向可以滚动,x轴方向不可滚动,这样表内容就能在自己的区域实现垂直方向的滚动了。3.将table-hea...
阅读 2325·2021-11-18 10:02
阅读 1884·2021-10-13 09:40
阅读 2976·2021-09-07 10:07
阅读 2081·2021-09-04 16:48
阅读 963·2019-08-30 13:18
阅读 2433·2019-08-29 14:03
阅读 2892·2019-08-29 12:54
阅读 3136·2019-08-26 11:41