本身作的項目碰到這樣一個需求,就是對全部的表格添加表頭能夠拖動的效果。我一想,這不簡單,分分鐘鍾給你作出來。拿起個人電腦,啪啪啪就敲起來了。
必定是哪裏不對,以個人聰明才智,結果應該不是這樣的,而後淨下心來,好好理了下思路後,總算是作出來了。
至於結果嘛,我確定是作出來的,像下面這種: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,又發現了一個問題,就是拖動最後一列時。。。我想一想,先睡了==