【前端】表格合并如何实现?

简言

介绍实现表格合并的一种方法。

表格合并

表格合并操作是一个比较复杂的操作,它主要分为以下步骤:

  1. 获取选中区域
  2. 选择合并显示的单元格
  3. 实现合并操作。

我们就逐一实现这三步,最后实现一个较完整的合并操作。(不考虑边界情况)

获取选中区域

选中区域这里相对来说比较难,它是第一步,也是最重要的一步,只要选的不对,白搭。
还有就是正常的选区,它可以有以下四种选中方向:
在这里插入图片描述

这里只考虑第3种,其他的可自行实现(利用x和y差值方向)。

另外,还有就是选区取消实现,例如我选中了2-3,2-4,然后我的鼠标又移回2-3区域了,那么2-4就应该取消选中。

思路

这里我选择的是利用鼠标按下、移动、抬起事件来实现长按选中操作,期间记录选中的节点和范围,以及最后选中节点的位置。
代码在示例。

选择合并显示的单元格

要选择合并显示的单元格,首先要判断你怎么选区的(选区方向)。
因为table元素中,一般都是靠前的td元素修改colspan和rowspan属性来执行合并操作。

示例代码 只考虑了 正向选区一种,即默认第一个为靠前td元素

代码在示例。

实现合并操作

合并操作这里主要处理选中区域的单元格,根据选中个数和合并情况来处理合并操作。

示例实现的是右键合并操作

在这里插入图片描述

示例

<!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></table><!-- 表格右键 --><div class="show-box"><div>向下添加一行</div><div>向上添加一行</div><div>删除当前行行</div><div class="merge-cell">合并</div></div><script>const table = document.querySelector('.zsk-table')const showBox = document.querySelector('.show-box')const mergeDiv = document.querySelector('.merge-cell')const select = {  // 选中单元格value: [[]],range: [[], []] //  [start,end]范围}//  合并命令mergeDiv.addEventListener('click', () => {if (select.value.length === 0) returnconsole.log(select.range, 'range');//  默认是正向选中,即结尾点比开始点的x和y都大select.value.forEach((item, i) => {item.forEach((v, k) => {if (i === 0 && k === 0) {console.log(v, '显示项');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()console.log(e.target, '右键', e)showBox.style.left = e.clientX + 'px'showBox.style.top = e.clientY + 'px'})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 = 0table.addEventListener('mousedown', (e) => {if (timer !== 0) {clearTimeout(timer)timer = 0}timer = setTimeout(() => {//  先清空clearSelect()run = truestartRange = [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] = startRangeselect.range[1] = [startRange[0] + e.target.offsetWidth, startRange[1] + e.target.offsetHeight]}}, 200)})//  移动table.addEventListener('mousemove', (e) => {if (run) {end = [e.clientX, e.clientY]console.log(`x: ${end[0] - startRange[0]} y: ${end[1] - startRange[1]}  范围:${select.range[1][0] - select.range[0][0]}`);//  计算范围 然后 判断是否修改选中dom数组let x = end[0] - lastEnd[0]let y = end[1] - lastEnd[1]if (x > lastInfo[0]) {console.log('横向超出,x扩展');lastEnd = [select.range[1][0], lastEnd[1]]lastInfo = [e.target.offsetWidth, lastInfo[1]]//  每行横向添加一行for (let i = 0; i < select.value.length; i++) {//  查找最后一个节点元相邻td元素console.log(select.value[i]);let el = getNextElement(select.value[i][select.value[i].length - 1])select.value[i].push(el)}//  更新选取范围 xselect.range[1] = [select.range[1][0] + e.target.offsetWidth, select.range[1][1]]} else if (x < 0) {if (select.value[0].length <= 1) returnconsole.log(select.value[0].length, '当前个数');select.range[1] = [lastEnd[0], select.range[1][1]]lastEnd = [lastEnd[0] - e.target.offsetWidth, lastEnd[1]]lastInfo = [lastInfo[0], e.target.offsetHeight]//  减去每行的最后一个for (let i = 0; i < select.value.length; i++) {if (select.value[i].length > 0) {select.value[i][select.value[i].length - 1].classList.remove('select')select.value[i].pop()}}}if (y > lastInfo[1]) {console.log('纵向超出,y扩展', select.value[0].length);lastEnd = [lastEnd[0], select.range[1][1]]lastInfo = [lastInfo[0], e.target.offsetHeight]const lastRow = []for (let k = 0; k < select.value[0].length; k++) {let el = select.value[select.value.length - 1][k]lastRow.push(getNextRowXElement(el))}select.value.push(lastRow)//  更新选区范围select.range[1] = [select.range[1][0], select.range[1][1] + e.target.offsetHeight]} else if (y < 0) {if (select.value.length < 1) returnselect.range[1] = [select.range[1][0], lastEnd[1]]lastEnd = [lastEnd[0], lastEnd[1] - e.target.offsetHeight]lastInfo = [lastInfo[0], e.target.offsetHeight]//  去掉最后一行的classselect.value[select.value.length - 1].forEach(el => {el.classList.remove('select')})select.value.pop()}//  选中元素添加classfor (let i = 0; i < select.value.length; i++) {for (let k = 0; k < select.value[i].length; k++) {select.value[i][k].classList.add('select')}}// select.value.push(e.target)// e.target.classList.add('select')}})//  抬起table.addEventListener('mouseup', (e) => {run = falseif (timer !== 0) {clearTimeout(timer)timer = 0}})}/*获取下一行当前横坐标相同位置元素*/function getNextRowXElement(currentElement) {let nextElement = currentElement.parentElement.nextElementSibling.firstElementChild;let currentLeft = currentElement.offsetLeft;let nextElementLeft = nextElement.offsetLeft;while (nextElement !== null && nextElementLeft !== currentLeft) {nextElement = getNextElement(nextElement);nextElementLeft = nextElement.offsetLeft;}return nextElement;}/***  获取下一个兄弟元素**/function getNextElement(element) {if (element.nextElementSibling) {return element.nextElementSibling;} else {return nulllet parent = element.parentElement;while (parent && parent.nextElementSibling === null) {parent = parent.parentElement;}return parent ? parent.nextElementSibling.firstElementChild : null;}}function clearSelect() {select.value.forEach((item, index) => {item.forEach(v => {v.classList.remove('select')})})Object.assign(select, {value: [[]],range: [[], []] //  [start,end]范围})}</script>
</body></html>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

问题

  • 选中区域方向问题
  • 选中节点信息没有处理colspan和rowspan属性,导致无法再次合并。
  • 无法再次合并。
  • 事件触发较频繁

结语

结束了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/830512.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

区块链交易所开发

在当今数字化时代&#xff0c;区块链技术以其独特的去中心化、安全性和透明性&#xff0c;正在逐步改变我们的生活。其中&#xff0c;区块链交易所作为连接区块链技术与广大投资者的桥梁&#xff0c;其开发与发展备受关注。本文将从技术进步与市场需求两个维度&#xff0c;探讨…

mySQL商城项目实战 (终)(全部表)(1-88张)

本章无sql语句&#xff0c;直接放转出的sql文件。 88张表结果如图! 资源在已经与文章绑定&#xff0c; 在navicat工具中&#xff0c;执行以下步骤 在新建的数据库中右键,点击【运行sql文件】&#xff0c;运行绑定的资源&#xff0c;之后您就可以在您的navicat中看到我建好的8…

Kafka 3.x.x 入门到精通(08)——对标尚硅谷Kafka教程

Kafka 3.x.x 入门到精通&#xff08;08&#xff09;——对标尚硅谷Kafka教程 5. Kafka优化5.1 资源配置5.1.1 操作系统5.1.2 磁盘选择5.1.3 网络带宽5.1.4 内存配置5.1.5 CPU选择 5.2 集群容错5.2.1 副本分配策略5.2.2 故障转移方案5.2.3 数据备份与恢复 5.3 参数配置优化5.4 数…

【c++】mutable是一个关键字,用于指定一个类成员可以在一个const成员函数中被修改。

mutable是一个关键字,用于指定一个类成员可以在一个const成员函数中被修改。通常,当一个成员函数被声明为const时,这意味着这个函数不能修改它所属的对象。然而,有时候你可能需要在一个const成员函数中修改某个成员变量。这时,你就可以使用mutable关键字。webrtc的StunReq…

机器学习:深入解析SVM的核心概念(问题与解答篇)【三、核函数】

核函数 **问题一&#xff1a;为什么说是有限维就一定存在高维空间可分呢&#xff1f;**原始空间与特征空间为什么映射到高维空间可以实现可分核函数的作用 **问题二&#xff1a;最终怎么得到函数**从对偶问题到决策函数的步骤&#xff1a;结论 **问题三&#xff1a;为什么说特征…

在国内 PMP 有多少含金量?

PMP认证并不是对所有人都有价值&#xff0c;也并不是考到它必须会升值加薪&#xff0c;那可能就有人会问了&#xff0c;那我为什么还要考PMP&#xff1f;此言差矣&#xff0c;我个人项目管理行业混迹了这么多年了&#xff0c;真正对我有用的证书除了学历以外就是PMP认证了&…

Ubuntu 24.04 LTS (Noble Numbat) 正式版发布

Ubuntu 24.04 LTS (Noble Numbat) 正式版发布 Canonical 的第 10 个长期支持版本在性能工程、企业安全和开发人员体验方面树立了新标准 请访问原文链接&#xff1a;Ubuntu 24.04 LTS (Noble Numbat) 正式版发布&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。…

搭建基础镜像(centos+jdk)

搭建基础镜像&#xff08;centosjdk&#xff09; 1. 目录结构1.1 应用目录2.2 镜像目录 2. 编写Dockerfile2.1 设置工作目录2.2 解决时间同步问题&#xff08;设置时区&#xff09;2.3 核心逻辑2.4 设置环境变量 3. 构建镜像3.1 构建镜像3.2 导出镜像 1. 目录结构 1.1 应用目录…

10.MMD 室内场景导入背景视频和灯光

导入背景视频 1. 导入人物和场景 场景是Akali’s room&#xff0c;可以在墙壁上添加视频 先添加主场景 2. 修改视频文件格式 在背景里选择导入背景视频文件 需要将mp4视频格式转化为AVI格式 方法一 先将视频导入格式工厂 点击配置 将视频编码改成DivX 再开始处理 …

DockerUI安装使用

DockerUI安装使用 主机环境 [roottest01 ~]# uname -a Linux test01 3.10.0-862.el7.x86_64 #1 SMP Fri Apr 20 16:44:24 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux [roottest01 ~]# cat /etc/redhat-release CentOS Linux release 7.5.1804 (Core)安装 [roottest01 ~]# doc…

【算法基础实验】图论-UnionFind连通性检测之quick-find

Union-Find连通性检测之quick-find 理论基础 在图论和计算机科学中&#xff0c;Union-Find 或并查集是一种用于处理一组元素分成的多个不相交集合&#xff08;即连通分量&#xff09;的情况&#xff0c;并能快速回答这组元素中任意两个元素是否在同一集合中的问题。Union-Fin…

分布式存储 Ceph 的演进经验

从 2004 年到今天&#xff0c;Ceph 的存储后端一直都在演变&#xff0c;从最开始基于 B 树的 EBOFS 演变到今天的 BlueStore&#xff0c;存储后端已经变得非常成熟&#xff0c;新的存储系统不仅能够提供良好的性能&#xff0c;还有着优异的兼容性。我们在这篇文章中将要简单介绍…

Android SQLiteDatabase的使用详解

1、数据库–公共变量&#xff1a; 2、数据库–打开&#xff1a; 3、数据库–增&#xff1a; 4、数据库–删&#xff1a; 5、数据库–改&#xff1a; 6、数据库–查&#xff1a; 7、数据库–关闭&#xff1a; 8、数据库–辅助工具&#xff1a; 9、数据库–效果&…

配置DHCP和DNS

DHCP DHCP原理 作用&#xff1a;是一种网络协议&#xff0c;用于自动分配IP地址、子网掩码、默认网关、DNS服务器等TCP/IP参数 1.DHCP的四个报文 1.discover报文&#xff1a; 找寻dhcp服务器 2.offer报文&#xff1a; 服务器回复discover报文并且携带网络配置信息&#xff…

我在公司干了两年,有个在公司工作三年的成员要离职,接手别人代码才发现真的是一言难尽

微服务框架是别的团队的人搭建的&#xff0c;他负责单独开发一个报表模块&#xff0c;这是初始版本&#xff0c;还未上线 1、nacos做注册中心&#xff0c;却胡乱注册&#xff0c;服务命名有下划线 测试环境nacos配置命名空间为dev&#xff0c;直接与其他的微服务test命名空间…

Qt QLineEdit详解

1.简介 QLineEdit是一个单行文本编辑器。 行编辑允许用户使用一组有用的编辑功能输入和编辑单行纯文本&#xff0c;包括撤消和重做、剪切和粘贴以及拖放。 通过更改行编辑的echoMode&#xff0c;它也可以用作“只写”字段&#xff0c;用于密码等输入。 文本的长度可以限制为ma…

毅四捕Go设计模式笔记——命令模式

命令模式&#xff08;Command Pattern&#xff09; 为了解决什么问题&#xff1f; 命令模式的目的是将请求发起者和请求执行者解耦&#xff0c;使得请求的发起者不需要知道具体的执行者是谁&#xff0c;也不需要知道执行的具体过程&#xff0c;只需要发送请求即可。 通过使用…

如何轻松在D盘新建文件夹?意外丢失的文件夹怎么找回

对于很多刚接触电脑的朋友来说&#xff0c;如何正确地新建文件夹并将其放置在特定盘符&#xff08;如D盘&#xff09;可能是一个不小的挑战。同时&#xff0c;如果新建的文件夹突然消失&#xff0c;而我们又确信自己没有删除它&#xff0c;那么该如何找回呢&#xff1f;本文将为…

直播间怎么提高流量?巨量千川官方真实投流助力获客轻松翻倍

随着互联网的快速发展&#xff0c;直播已经成为了一种极具吸引力和互动性的娱乐和营销方式。然而&#xff0c;如何提高直播间的流量&#xff0c;(直播间流量&#xff1a;kxs7667)吸引更多观众成为了每个直播主都关注的重要问题。与此同时&#xff0c;巨量千川官方真实投流作为一…

如何在WordPress中设置网站的SEO标题和描述

在WordPress中&#xff0c;想要让你的网站在搜索引擎结果中脱颖而出&#xff0c;设置优秀的SEO标题和描述至关重要。这不仅可以帮助搜索引擎更好地理解你的网站内容&#xff0c;还可以吸引更多的点击率和流量。而选择一款合适的SEO插件是实现这一目标的关键之一。让我们来看看两…