preface

My own project encountered the need to add a draggable header effect to all tables. I thought, it’s not easy, I’ll make it for you in minutes. Pick up my computer, pop, pop, pop.



There must be something wrong, with my intelligence and wisdom, the result should not be so, and then clean down, after a good reason for thinking, finally is to do it.

As for the results, I’m sure I made them, like the following:

To prepare

First of all, it should be noted that the tables used in our project can only be divided into two categories: one is the table header is not fixed, that is, ordinary table, and the other is the table header is fixed.tbodyPart of it is scrollable. It needs to be noted that the type of fixed table header is the need to use twotableTo achieve, do people should also understand. The former looks simpler because the width is affectedtheadIn thethIf you use two tables, the following situation will occur:



Emmm, this should be different from what we imagined, how to do this, feel it is very troublesome to deal with ah. Remember seenelement-uiThe table appears to have the implementation of dragging the table header, first open the console to see the structure:



Well, I haven’t used it my whole life<colgroup>and<col>These two labels, but look closely and there’s awidthI think I know what’s going on here. Open itMDNIf you look at the description of the property, as expected,widthCan control the width of the current column.

We have solved the width control, and there is another problem, that is, how to change the width of other columns after dragging, as follows:

a b c d

If I drag a column, how should change the width of the assigned to b, c, d, I here is such processing, b, c, d a attribute to say whether the column has been dragging, if they have not drag, b, c, d, then a change in the width of the split to the width of the three columns b, c, d, if b, c, d are changed the words, So just change the width of the last column, D. Ok, so we have the idea, and we can implement it.

implementation

First, the HTML structure looks something like this:

<table>
  <thead>
    <tr>
      <th>a<th>
      <th>b<th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>1<th>
      <th>2<th>
    </tr>
  </tbody>
</table>Copy the code

Js aspects

constructor (id, options) { this._el = document.querySelector(`#${id}`); This._tables = array. from(this._el.querySelectorAll('table')); setTimeout(() => this._resolveDom()); This.store = {dragging: false, // Drag draggingColumn: null, // Drag object miniWidth: 30, // Drag minimum width startMouseLeft: ClientX startLeft: undefined, //th Right distance from table startColumnLeft: undefined, //th left distance from table tableLeft: Columns: [], BColumns: [],}; };Copy the code

Add the 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); 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); } // drag placeholder line const hold = document.createElement('div'); hold.classList.add('resizable-hold'); this._el.appendChild(hold);Copy the code

For reuse, we split the table into two tables regardless of whether the table header is fixed or not, which is also much easier to process. Then we move the finger to the right of the cursor and set the value of the cursor to 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; }}};Copy the code

Note that getBoundingClientRect() gets rigth from the right side of the element to the left edge of the page, not the right edge of the page. Add the mousemove event to Thead’s TR. When the mouse pointer is less than 8 to the right edge, change the shape of the pointer and then change the state in store to indicate that the click can be dragged. 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'; }; For example 🌰, if a, B, C, and D each have a changed state, default false, drag past A, A.c hanged to true, the changed width is amortized by the remaining B, C, and D, if all are changed, (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; SetTimeout (() => {target.classlist.remove ('noclick'); }, 0); }; document.addEventListener('mouseup', handleOnMouseUp); document.addEventListener('mousemove', handleOnMouseMove); }}; Tr.addEventListener('mousedown', handleMouseDown);Copy the code

preview

conclusion

Feel very interesting and useful things, but also let yourself up a lot of posture, source code, has been made into the form of a class, the use is relatively simple, because it is suddenly proposed requirements, has not done too much testing, there may be do not know the bug.

blessing

Write in the end, will soon be the New Year, the mood is very happy. So, I wish you all a happy New Year in advance here,, skin just happy, hey hey. Bye bye ~