可拖動table表頭的實現

前言

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

相關文章
相關標籤/搜索