原生JS模拟Excel表格单元格合并,拆分,删除指定列示例
原生JS模拟Excel表格单元格合并,拆分,删除指定列示例,效果如下
<!doctype html> <style> table { border-collapse: collapse } table td { border: solid 1px #ccc; padding: 10px 20px; word-break: break-all; } .cannotselect { -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none; -khtml-user-select: none; user-select: none; } td.selected { background: #0094ff; color: #fff } </style> <table border="1" id="tb1"> <tr> <td colspan="2" id="t1">0-0,0-1</td> <td id="t2" rowspan="2">0-2<br />1-2</td> <td id="t3">0-3</td> <td id="t4">0-4</td> </tr> <tr> <td id="t5">1-0</td> <td id="t6">1-1</td> <td id="t7" colspan="2">1-3,1-4</td> </tr> <tr> <td rowspan="2" id="t8">2-0<br />3-0</td> <td id="t9">2-1</td> <td colspan="2" id="t10">2-2,2-3</td> <td id="t11">2-4</td> </tr> <tr> <td id="t12">3-1</td> <td colspan="3" id="t13">3-2,3-3,3-4</td> </tr> </table> <hr /> <input type="button" onclick="megerCells()" value="合并选中单元格" /> <input type="button" onclick="breakCells()" value="取消合并选中单元格" /> <input type="button" onclick="deleteColumn ()" value="删除指定列" /> <script> /** * 请保留作者(showbo)信息,有bug不迷路,哈哈~~。 https://www.w3dev.cn/ */ //获取单元格原始rowIndex和cellIndex function getRC(curTd/*:HTMLTableCellElement*/, totalCell/*:int*/) { let tbody = curTd.parentNode.parentNode; //从第一行计算总的td数量,最后一行也可以,其他行计算不准确 let cellIndex = -1; let rowIndex = curTd.parentNode.rowIndex; if (curTd.parentNode.cells.length == totalCell) {//没有被rowspan,colspan影响到的单元格 cellIndex = curTd.cellIndex; } else { //被rowspan影响,往上找rowspan的行 cellIndex = curTd.cellIndex; for (let i = rowIndex - 1; i >= 0; i--) { for (let td of tbody.rows[i].cells) { if (td.rowSpan > 1) { if (td.parentNode.rowIndex + td.rowSpan > rowIndex && curTd.offsetLeft > td.offsetLeft) {//curTd所在行下标和当前rowspan重合,并且处于curTd前(使用位置来定位) cellIndex += td.colSpan;//加上次单元格colSpan } } } } //同一行中td的colspan合并计算 for (let i = curTd.cellIndex - 1; i >= 0; i--) { cellIndex += curTd.parentNode.cells[i].colSpan - 1; } } return JSON.stringify({ startRowIndex: rowIndex, startCellIndex: cellIndex, endRowIndex: rowIndex + curTd.rowSpan - 1, endCellIndex: cellIndex + curTd.colSpan - 1 }); } //初始表格的原始rowIndex,cellIndex及跨行,跨列信息 function initTdRc() { table.classList.add('cannotselect'); let firstRow = table.rows[0]; //用第一行计算总的td数量,其他行计算不准确 totalCell = 0; for (let td of firstRow.cells) { totalCell += td.colSpan; } for (let i = 0; i < table.rows.length; i++) { for (let j = 0; j < table.rows[i].cells.length; j++) { table.rows[i].cells[j].setAttribute('rc', getRC(table.rows[i].cells[j], totalCell)); } } } function removeAllSelectedClass() {//删除td选中样式 for (let tr of table.rows) { for (let td of tr.cells) { td.classList.remove('selected') } } } //在范围内td添加选中高亮样式 function addSelectedClass() { for (let tr of table.rows) { for (let td of tr.cells) { let rc = JSON.parse(td.getAttribute('rc')/* as string*/); //在范围内加上高亮样式 if (rc.startRowIndex >= MMRC.startRowIndex && rc.endRowIndex <= MMRC.endRowIndex && rc.startCellIndex >= MMRC.startCellIndex && rc.endCellIndex <= MMRC.endCellIndex) { td.classList.add('selected') } } } } function checkMMRC() {//检查选中范围的rowspan和colspan let rangeChange = false; for (let tr of table.rows) { for (let td of tr.cells) { let rc = JSON.parse(td.getAttribute('rc')/* as string*/); //判断单元格4个顶点是否在范围内 if ( (rc.startRowIndex >= MMRC.startRowIndex && rc.startRowIndex <= MMRC.endRowIndex && rc.startCellIndex >= MMRC.startCellIndex && rc.startCellIndex <= MMRC.endCellIndex) ||//左上 (rc.endRowIndex >= MMRC.startRowIndex && rc.endRowIndex <= MMRC.endRowIndex && rc.startCellIndex >= MMRC.startCellIndex && rc.startCellIndex <= MMRC.endCellIndex) ||//左下 (rc.startRowIndex >= MMRC.startRowIndex && rc.startRowIndex <= MMRC.endRowIndex && rc.endCellIndex >= MMRC.startCellIndex && rc.endCellIndex <= MMRC.endCellIndex) ||//右上 (rc.endRowIndex >= MMRC.startRowIndex && rc.endRowIndex <= MMRC.endRowIndex && rc.endCellIndex >= MMRC.startCellIndex && rc.endCellIndex <= MMRC.endCellIndex) //右下 ) {//debugger let startRowIndex = Math.min.call(null, MMRC.startRowIndex, rc.startRowIndex); let endRowIndex = Math.max.call(null, MMRC.endRowIndex, rc.endRowIndex); let startCellIndex = Math.min.call(null, MMRC.startCellIndex, rc.startCellIndex); let endCellIndex = Math.max.call(null, MMRC.endCellIndex, rc.endCellIndex); if (MMRC.startRowIndex > startRowIndex) { MMRC.startRowIndex = startRowIndex; rangeChange = true; } if (MMRC.startCellIndex > startCellIndex) { MMRC.startCellIndex = startCellIndex; rangeChange = true; } if (MMRC.endRowIndex < endRowIndex) { MMRC.endRowIndex = endRowIndex; rangeChange = true; } if (MMRC.endCellIndex < endCellIndex) { MMRC.endCellIndex = endCellIndex; rangeChange = true; } } } } //范围有变化继续扩展 if (rangeChange) { checkMMRC(table); } } function onMousemove(e/*:any*/) {//鼠标在表格单元格内移动事件 let o = e.target;//as HTMLTableCellElement if (o.tagName == 'TD' && ((endTD != o && startTD != o) || (endTD && startTD == o))) {//不在开始td和结束td移动时再触发检查,优化下 endTD = o; removeAllSelectedClass(table); let startRC = JSON.parse(startTD.getAttribute('rc')/* as string*/), endRC = JSON.parse(endTD.getAttribute('rc')/* as string*/); //求2个单元格的开始rowIndex,结束rowIndex,开始cellIndex和结束cellIndex let startRowIndex = Math.min.call(null, startRC.startRowIndex, endRC.startRowIndex); let endRowIndex = Math.max.call(null, startRC.endRowIndex, endRC.endRowIndex); let startCellIndex = Math.min.call(null, startRC.startCellIndex, endRC.startCellIndex); let endCellIndex = Math.max.call(null, startRC.endCellIndex, endRC.endCellIndex); MMRC = { startRowIndex, startCellIndex, endRowIndex, endCellIndex }; checkMMRC(table); addSelectedClass(table); } } function onMouseup() { table.removeEventListener('mousemove', onMousemove); table.removeEventListener('mouseup', onMouseup); } function getText() { var text = []; for (let tr of table.rows) { let hit = false; for (let td of tr.cells) { if (td.classList.contains('selected')) { text.push(td.innerHTML); } } } return text.join(','); } function megerCells(/*:HTMLTableElement*/) {//合并单元格 if (startTD && endTD && startTD != endTD) {//开始结束td不相同确认合并 let tds = Array.from(table.querySelectorAll('td.selected')), firstTD = tds[0] , html = getText(); for (let i = 1; i < tds.length; i++)tds[i].parentNode.removeChild(tds[i]); firstTD.innerHTML = html; //更新合并的第一个单元格的缓存rc数据为所跨列和行 firstTD.setAttribute('colspan', MMRC.endCellIndex - MMRC.startCellIndex + 1) firstTD.setAttribute('rowspan', MMRC.endRowIndex - MMRC.startRowIndex + 1); firstTD.setAttribute('rc', JSON.stringify(MMRC)); } removeAllSelectedClass(table); MMRC = startTD = endTD = null; } function getInsertCellIndex(nextTr, offsetRight/*:int*/) {//找到拆分单元格时出现rowspan插入到新行中的单元格下标 for (let td of nextTr.cells) { if (Math.abs(td.offsetLeft - offsetRight) < 2)//注意这里内容宽度会出现小数点,但是用offsetWidth取值是整数有舍入操作,所以要取差值 return td.cellIndex; } return 0//找不到说明是在第一列合并的,返回0 } function breakCells() { if (MMRC) { if (MMRC.startRowIndex == MMRC.endRowIndex && MMRC.startCellIndex == MMRC.endCellIndex) { alert('无法拆分!'); return } var rows = Array.from(table.rows), cells; for (let tr of rows) { cells = Array.from(tr.cells);//拷贝到数组,而不是直接遍历tr.cells,cells会受cellspan,rowspan影响 for (let td of cells) { let rc = JSON.parse(td.getAttribute('rc')/* as string*/); if (!rc) continue;//rowspan新增的单元格跳过 //在范围内 if (rc.startRowIndex >= MMRC.startRowIndex && rc.endRowIndex <= MMRC.endRowIndex && rc.startCellIndex >= MMRC.startCellIndex && rc.endCellIndex <= MMRC.endCellIndex) { let colSpan = rc.endCellIndex - rc.startCellIndex; if (colSpan > 0) {//跨列 for (let i = 0, j = colSpan; i < j; i++) { tr.insertCell(td.cellIndex+1);//这个是在后面插入,前面插入+1 } td.colSpan = 1; } let rowSpan = rc.endRowIndex - rc.startRowIndex; if (rowSpan > 0) {//跨行 for (let k = 1; k <= rowSpan; k++) { let nextTr = table.rows[rc.startRowIndex + k]; let cellIndex = getInsertCellIndex(nextTr, td.offsetLeft+td.offsetWidth); for (let i = 0; i < colSpan + 1; i++) { nextTr.insertCell(cellIndex); } } td.rowSpan = 1; } } } } } removeAllSelectedClass(); initTdRc();//重新初始化过rc属性 } function onMousedown(e/*:any*/) {//鼠标按下事件 let o = e.target; if (o.tagName == 'TD') { removeAllSelectedClass(table); //绑定事件 endTD = startTD = o; table.addEventListener('mousemove', onMousemove); table.addEventListener('mouseup', onMouseup); startTD.classList.add('selected'); MMRC = JSON.parse(o.getAttribute('rc')/*as string*/); } } function deleteColumn() { let col = parseInt(prompt('请输入原始列下标!')); if (isNaN(col) || col >= totalCell) { alert(`列下标需要介于0~${totalCell - 1}之间!`); return; } for (let tr of table.rows) { for (let td of tr.cells) { var rc = JSON.parse(td.getAttribute('rc')); if (rc.startCellIndex <= col && col <= rc.endCellIndex) { if (rc.startCellIndex == rc.endCellIndex) {//只有一个,删掉 td.parentNode.removeChild(td); } else { td.colSpan -= 1; } break;//后续单元格不需要再遍历在,直接下一行 } } } initTdRc();//重新初始化rc } let table = document.querySelector('#tb1'), startTD, endTD, MMRC = { startRowIndex: -1, startCellIndex: -1, endRowIndex: -1, endCellIndex: -1 }, totalCell=0; initTdRc(table) table.addEventListener('mousedown', onMousedown); </script>
加支付宝好友偷能量挖...
原创文章,转载请注明出处:原生JS模拟Excel表格单元格合并,拆分,删除指定列示例