简言
表格合并基础篇
本篇是在上一章的基础上实现,实现了的功能有添加行、删除行、逆向选区、取消合并功能。
功能实现
添加行
添加行分为在上面添加和在下面追加行。
利用 insertAdjacentElement 方法实现,该方法可以实现从前插入元素和从后插入元素。
删除行
删除当前行就是利用元素remove()方法,从dom树种删除元素。
逆向选区
逆向选区是指选区从下往上选。
解决思路:记录当前选区时鼠标移动方向,往左上移动则为负,往右下移动则为正。负时在首位插入选中节点,正时从尾部追加选中节点,这样合并只需取第一个选中节点即可。
取消合并
获取当前元素的rowspan和colspan属性值,然后遍历后面的和下面包含行节点,删除节点的hide类,然后删除当前元素rowspan和colspan属性即可。
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表格合并</title>
<style>
.zsk-table {
border-collapse: collapse;
border: 1px solid;
font-family: inherit;
user-select: none;
}
.zsk-table tr {
height: 32px;
}
.zsk-table td {
border: 1px solid;
height: 32px;
padding: 16px;
}
.amount {
width: 100px;
}
.show-box {
position: absolute;
top: -200px;
left: -200px;
width: 200px;
background-color: #eee;
}
.show-box>div {
width: 200px;
height: 50px;
line-height: 50px;
border-bottom: 1px solid #000;
}
.show-box>div:hover {
background-color: #ccc;
cursor: pointer;
}
.select {
color: #fff;
background-color: #3987cf;
}
.hide {
display: none;
}
</style>
</head>
<body>
<h1>表格合并</h1>
<table tabindex="1" class="zsk-table">
<tr>
<td>1-1</td>
<td>1-2</td>
<td>1-3</td>
<td>1-4</td>
<td>1-5</td>
</tr>
<tr>
<td>2-1</td>
<td>2-2</td>
<td>2-3</td>
<td>2-4</td>
<td>2-5</td>
</tr>
<tr>
<td>3-1</td>
<td>3-2</td>
<td>3-3</td>
<td>3-4</td>
<td>3-5</td>
</tr>
<tr>
<td>4-1</td>
<td>4-2</td>
<td>4-3</td>
<td>4-4</td>
<td>4-5</td>
</tr>
<tr>
<td>5-1</td>
<td>5-2</td>
<td>5-3</td>
<td>5-4</td>
<td>5-5</td>
</tr>
</table>
<!-- 表格右键 -->
<div class="show-box">
<div class="add-down">向下添加一行</div>
<div class="add-up">向上添加一行</div>
<div class="delete-cell">删除当前行行</div>
<div class="merge-cell">合并</div>
<div class="split-cell">取消合并</div>
</div>
<script>
const table = document.querySelector('.zsk-table')
const showBox = document.querySelector('.show-box')
const mergeDiv = document.querySelector('.merge-cell')
const addDownDiv = document.querySelector('.add-down')
const addUpDiv = document.querySelector('.add-up')
const deleteCellDiv = document.querySelector('.delete-cell')
const cancelDiv = document.querySelector('.split-cell')
const select = { // 选中单元格
value: [[]],
range: [[], []] // [start,end]范围
}
// 向下添加一行
addDownDiv.addEventListener('click', (e) => {
let node = select.value[0][0].parentElement
let newNode = node.cloneNode(true)
node.insertAdjacentElement('afterend', newNode)
clearSelect()
})
// 向上添加一行
addUpDiv.addEventListener('click', (e) => {
let node = select.value[0][0].parentElement
let newNode = node.cloneNode(true)
node.insertAdjacentElement('beforebegin', newNode)
clearSelect()
})
// 删除当前行
deleteCellDiv.addEventListener('click', () => {
let node = select.value[0][0].parentElement
node.remove()
clearSelect()
})
// 取消合并
cancelDiv.addEventListener('click', () => {
let node = select.value[0][0]
let rowspan = node.getAttribute('rowspan')
let colspan = node.getAttribute('colspan')
if (!colspan || !rowspan) return
colspan = Number.parseInt(colspan)
rowspan = Number.parseInt(rowspan)
let index = getChildIndex(node)
let nextNode = node
for (let i = 0; i < rowspan; i++) {
let col = colspan
let temp = nextNode
while (col--) {
temp.classList.remove('hide')
temp = temp.nextElementSibling
}
nextNode = getRowXElement(nextNode, 1)
}
node.removeAttribute('colspan')
node.removeAttribute('rowspan')
})
// 合并命令
mergeDiv.addEventListener('click', () => {
if (select.value.length === 0) return
// 默认是正向选中,即结尾点比开始点的x和y都大
select.value.forEach((item, i) => {
item.forEach((v, k) => {
if (i === 0 && k === 0) {
v.setAttribute('colspan', item.length || '1')
v.setAttribute('rowspan', select.value.length || '1')
} else {
v.classList.add('hide')
}
})
})
clearSelect()
})
// 右键
table.addEventListener('click', (e) => {
e.target.focus()
})
table.addEventListener("contextmenu", (e) => {
e.preventDefault()
showBox.style.left = e.clientX + 'px'
showBox.style.top = e.clientY + 'px'
if (!select.value[0][0]) {
select.value[0][0] = e.target
}
})
table.addEventListener('blur', (e) => {
setTimeout(() => {
showBox.style.left = -1000 + 'px'
showBox.style.top = -1000 + 'px'
}, 150)
})
/**
* 选中逻辑
*
**/
selectLogic(table, select)
function selectLogic(table, select) {
let lastEnd = [0, 0] // 最后选中的单元格位置
let lastInfo = [0, 0] // 最后选中单元格的宽高
let endUp = [0, 0]
let startRange = [0.0]
let endRange = [0, 0]
let run = false
// 按下
let timer = 0
table.addEventListener('mousedown', (e) => {
if (timer !== 0) {
clearTimeout(timer)
timer = 0
}
timer = setTimeout(() => {
// 先清空
clearSelect()
run = true
startRange = [e.clientX - e.offsetX, e.clientY - e.offsetY]
lastEnd = [startRange[0], startRange[1]]
lastInfo = [e.target.offsetWidth, e.target.offsetHeight]
e.target.classList.add('select')
if (e.target.tagName === 'TD') {
select.value[0].push(e.target)
select.range[0] = startRange
select.range[1] = [startRange[0] + e.target.offsetWidth, startRange[1] + e.target.offsetHeight]
}
}, 200)
})
// 移动
table.addEventListener('mousemove', (e) => {
if (run) {
end = [e.clientX, e.clientY]
// 计算范围 然后 判断是否修改选中dom数组
let x = end[0] - lastEnd[0]
let y = end[1] - lastEnd[1]
let xDirection = end[0] - startRange[0] > 0 ? 1 : -1 // x方向
let yDirection = end[1] - startRange[1] > 0 ? 1 : -1 // y方向
console.log(`x: ${x} y: ${y} 方向x:${xDirection} 方向y:${yDirection}`);
if ((xDirection === 1 && x >= lastInfo[0]) || (xDirection === -1 && x <= 0)) {
console.log('横向超出,x扩展');
// 更新选取范围 x
if (xDirection === 1) {
lastEnd = [select.range[1][0], lastEnd[1]]
lastInfo = [e.target.offsetWidth, lastInfo[1]]
select.range[1] = [select.range[1][0] + e.target.offsetWidth, select.range[1][1]]
} else {
select.range[0] = [select.range[0][0] - e.target.offsetWidth, select.range[0][1]]
lastEnd = [select.range[0][0], lastEnd[1]]
lastInfo = [e.target.offsetWidth, lastInfo[1]]
}
// 每行横向添加一行
for (let i = 0; i < select.value.length; i++) {
// 查找最后一个节点元相邻td元素
console.log(select.value[i]);
if (xDirection === 1) {
let el = getElement(select.value[i][select.value[i].length - 1], xDirection)
select.value[i].push(el)
} else {
let el = getElement(select.value[i][0], xDirection)
select.value[i].unshift(el)
}
}
} else if ((xDirection === 1 && x <= 0) || (xDirection === -1 && x >= lastInfo[0])) {
if (select.value[0].length <= 1) return
console.log(select.value[0].length, '当前个数');
if (xDirection === 1) {
select.range[1] = [lastEnd[0], select.range[1][1]]
lastEnd = [lastEnd[0] - e.target.offsetWidth, lastEnd[1]]
lastInfo = [e.target.offsetWidth, lastInfo[1]]
} else {
select.range[0] = [lastEnd[0] + e.target.offsetWidth, select.range[0][1]]
lastEnd = [select.range[0][0], lastEnd[1]]
lastInfo = [e.target.offsetWidth, lastInfo[1]]
}
// 减去每行的最后一个
for (let i = 0; i < select.value.length; i++) {
if (select.value[i].length > 0) {
if (xDirection === 1) {
select.value[i][select.value[i].length - 1].classList.remove('select')
select.value[i].pop()
} else {
select.value[i][0].classList.remove('select')
select.value[i].shift()
}
}
}
}
if ((yDirection === 1 && y > lastInfo[1]) || (yDirection === -1 && y <= 0)) {
console.log('纵向超出,y扩展', select.value[0].length);
if (yDirection === 1) {
lastEnd = [lastEnd[0], select.range[1][1]]
lastInfo = [lastInfo[0], e.target.offsetHeight]
select.range[1] = [select.range[1][0], select.range[1][1] + e.target.offsetHeight]
} else {
select.range[0] = [select.range[0][0], select.range[0][1] - e.target.offsetHeight]
lastEnd = [lastEnd[0], select.range[0][1]]
lastInfo = [lastInfo[0], e.target.offsetHeight]
}
const lastRow = []
for (let k = 0; k < select.value[0].length; k++) {
let el = yDirection === 1 ? select.value[select.value.length - 1][k] : select.value[0][k]
lastRow.push(getRowXElement(el, yDirection))
}
if (yDirection === 1) {
select.value.push(lastRow)
} else {
select.value.unshift(lastRow)
}
// 更新选区范围
} else if ((yDirection === 1 && y <= 0) || (yDirection === -1 && y >= lastInfo[1])) {
if (select.value.length < 1) return
if (yDirection === 1) {
select.range[1] = [select.range[1][0], lastEnd[1]]
lastEnd = [lastEnd[0], lastEnd[1] - e.target.offsetHeight]
lastInfo = [lastInfo[0], e.target.offsetHeight]
} else {
select.range[0] = [select.range[0][0], lastEnd[1] + e.target.offsetHeight]
lastEnd = [lastEnd[0], lastEnd[1] + e.target.offsetHeight]
lastInfo = [lastInfo[0], e.target.offsetHeight]
}
// 去掉最后一行的class
if (yDirection === 1) {
select.value[select.value.length - 1].forEach(el => {
el.classList.remove('select')
})
select.value.pop()
} else {
select.value[0].forEach(el => {
el.classList.remove('select')
})
select.value.shift()
}
}
// 选中元素添加class
for (let i = 0; i < select.value.length; i++) {
for (let k = 0; k < select.value[i].length; k++) {
select.value[i][k] && select.value[i][k].classList.add('select')
}
}
}
})
// 抬起
table.addEventListener('mouseup', (e) => {
run = false
if (timer !== 0) {
clearTimeout(timer)
timer = 0
}
console.log(select.value);
})
}
/*
获取下一行当前横坐标相同位置元素
*/
function getRowXElement(currentElement, direction = 1) {
if (!currentElement.parentElement.nextElementSibling && direction == 1 || !currentElement.parentElement.previousElementSibling && direction !== 1) return null
let nextElement = direction === 1 ? currentElement.parentElement.nextElementSibling.firstElementChild : currentElement.parentElement.previousElementSibling.firstElementChild;
let childIndex = getChildIndex(currentElement) // 获取当前元素在父元素中的索引
if (childIndex !== -1) {
return nextElement.parentElement.children[childIndex]
} else {
let currentLeft = currentElement.offsetLeft;
let nextElementLeft = nextElement.offsetLeft;
while (nextElement !== null && nextElementLeft !== currentLeft) {
nextElement = getElement(nextElement, 1);
nextElementLeft = nextElement.offsetLeft;
}
return nextElement;
}
}
/**
* 获取下一个兄弟元素
* direction === 1时默认查找下一个兄弟元素,否则上一个
**/
function getElement(element, direction = 1) {
if (direction === 1) {
if (element.nextElementSibling) {
return element.nextElementSibling;
} else {
let parent = element.parentElement;
while (parent && parent.nextElementSibling === null) {
parent = parent.parentElement;
}
return parent ? parent.nextElementSibling.firstElementChild : null;
}
} else {
if (element.previousElementSibling) {
return element.previousElementSibling;
} else {
let parent = element.parentElement;
while (parent && parent.previousElementSibling === null) {
parent = parent.parentElement;
}
return parent ? parent.previousElementSibling.firstElementChild : null;
}
}
}
function clearSelect() {
select.value.forEach((item, index) => {
item.forEach(v => {
v.classList.remove('select')
})
})
Object.assign(select, {
value: [[]],
range: [[], []] // [start,end]范围
})
}
/**
* 获取子元素的索引
*/
function getChildIndex(child) {
var parent = child.parentNode;
for (var i = 0; i < parent.children.length; i++) {
if (parent.children[i] === child) {
return i;
}
}
return -1; // 如果找不到子元素,则返回-1
}
</script>
</body>
</html>
结语
合并没有做多次合并处理。
添加列没有实现。
边界情况未处理。