首先要說明的是,我們的項目使用的表格大概只分為兩類,一類是表頭不固定,就是普通的表格,另一類是表頭固定,tbody
#部分是可以滾動的。需要說明的是,表頭固定的那種是需要用兩個table
去實現,做過的人應該也都明白。前者看起來比較簡單,因為寬度是受thead
裡的th
影響的,後者看起來就不好處理,因為你用兩個table就會出現下面的情況:
emmm,這和我們想像的應該不一樣,這可咋整,感覺處理起來很麻煩啊。想起看過element-ui
中的表格,似乎有拖曳表頭的實現,先打開控制台看下結構吧:
呃,話說長這麼大我都沒用過<colgroup>
和<col>
這兩個標籤,但仔細觀察上面有個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結構大概是這樣的:
<table>
<thead>
<tr>
<th>a<th>
<th>b<th>
</tr>
</thead>
<tbody>
<tr>
<th>1<th>
<th>2<th>
</tr>
</tbody>
</table>
登入後複製
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('p');
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';
};
// 寬度是這樣分配的,舉個
登入後複製
以上是如何實作可拖曳table表頭的詳細內容。更多資訊請關注PHP中文網其他相關文章!