本文已参与「新人创作礼」活动,一起开启掘金创作之路
如图
html代码
使用方法: new tableResizable(id)
表格需要用一个div包裹住,然后传入div的id
关键技术点: 在表格内生成<col>标签
<col>标签为表格中一个或多个列定义属性值。
<col>标签添加 class 属性。这样就可以使用 CSS 来负责对齐方式、宽度和颜色等等。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
<script src="tableResizable.js"></script>
</head>
<body>
<html>
<body>
<div id="table">
<table border="1">
<thead>
<tr>
<th>Month</th>
<th>Savings</th>
<th>Month</th>
<th>Savings</th>
</tr>
</thead>
<tbody>
<tr>
<td>January</td>
<td>$100</td>
<td>January</td>
<td>$100</td>
</tr>
<tr>
<td>January</td>
<td>$100</td>
<td>January</td>
<td>$100</td>
</tr>
<tr>
<td>January</td>
<td>$100</td>
<td>January</td>
<td>$100</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
<script>
new tableResizable('table')
</script>
</body>
</html>
js代码
//表格宽度自由调整
class tableResizable {
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: [],
};
};
_saveCols(header, body) {
// cols
this.store.HColumns = Array.from(header.querySelectorAll('col')).map(v => ({
el: v,
isChange: false,
}));
this.store.BColumns = Array.from(body.querySelectorAll('col')).map(v => ({
el: v,
isChange: false,
}));
};
_resolveDom() {
const [THeader] = this._tables;
let TBody;
let Tr = [];
let cols = [];
if (THeader.tHead.rows.length > 1) {
$(THeader.tHead.rows).find('th').each(function(index, item) {
if (parseInt($(item).attr('colspan')) > 1) {
} else {
Tr.push($(item)[0])
}
})
Tr.forEach((item, index) => {
const col = document.createElement('col');
item.dataset.index = index;
col.width = +item.offsetWidth;
cols.push(col);
});
} else {
Tr = THeader.tHead.rows[0];
const columns = Tr ? Array.from(Tr.cells) : [];
cols = columns.map((item, index) => {
const col = document.createElement('col');
item.dataset.index = index;
col.width = +item.offsetWidth;
return col;
});
}
const Bcolgroup = document.createElement('colgroup');
cols.reduce((newDom, item) => {
newDom.appendChild(item);
return newDom;
}, Bcolgroup);
const HColgroup = Bcolgroup.cloneNode(true);
//不管是一个table还是两个,都把header合body提出来
if (this._tables.length === 1) {
const [, tbody] = Array.from(THeader.children);
tbody.remove();
var HFirstChild = THeader.firstChild;
THeader.insertBefore(HColgroup, HFirstChild);
TBody = THeader.cloneNode();
TBody.appendChild(Bcolgroup);
TBody.appendChild(tbody);
this._el.appendChild(TBody);
} else {
var HFirstChild = THeader.firstChild;
THeader.insertBefore(HColgroup, HFirstChild);
[, TBody] = this._tables;
var BFirstChild = TBody.firstChild;
TBody.insertBefore(Bcolgroup, BFirstChild);
}
//拖动时的占位线
const hold = document.createElement('div');
hold.classList.add('resizable-hold');
this._el.appendChild(hold);
// 把cols缓存起来
this._saveCols(THeader, TBody);
//处理事件
for (var i = 0; i < THeader.tHead.rows.length; i++) {
THeader.tHead.rows[i].addEventListener('mousemove', this.handleMouseMove.bind(this));
THeader.tHead.rows[i].addEventListener('mouseout', this.handleMouseOut.bind(this));
}
//处理拖动
const handleMouseDown = (evt) => {
if (this.store.draggingColumn) {
this.store.dragging = true;
let {
target
} = evt;
while (target && target.tagName !== 'TH') {
target = target.parentNode;
}
if (!target) return;
const tableEle = THeader;
const tableLeft = tableEle.getBoundingClientRect().left;
const columnRect = target.getBoundingClientRect();
const minLeft = columnRect.left - tableLeft + this.store.miniWidth;
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, i) => i > index && !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;
});
hold.style.display = 'none';
document.body.style.cursor = '';
this.store.dragging = false;
this.store.draggingColumn = null;
this.store.startMouseLeft = undefined;
this.store.startLeft = undefined;
this.store.startColumnLeft = undefined;
this.store.tableLeft = undefined;
}
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);
}
};
for (var i = 0; i < THeader.tHead.rows.length; i++) {
THeader.tHead.rows[i].addEventListener('mousedown', handleMouseDown);
}
};
handleMouseMove(evt) {
let {
target
} = evt;
while (target && target.tagName !== 'TH') {
target = target.parentNode;
}
if (!target) return;
if (!this.store.dragging) {
const rect = target.getBoundingClientRect();
const bodyStyle = document.body.style;
if (rect.width > 12 && rect.right - evt.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;
}
}
};
handleMouseOut() {
document.body.style.cursor = '';
}
}