前言
在网页开发中,创建交互式表格是很常见的。今天我们通过一个示例,来展示如何使用 HTML、CSS 和 JavaScript 实现一个能够动态添加和删除行的表格,并详细解释其中 JavaScript 部分的代码逻辑。
功能展示
- 初始状态:页面加载后,表格已经存在一行数据,包含序号、内容以及一个删除按钮。
- 添加行:点击 “添加行” 按钮,表格会新增一行,内容列的数据每次按固定值递增。
- 删除行:点击每行中的 “删除” 按钮,对应的行将从表格中移除。
- 序号与颜色更新:每次添加或删除行后,表格的序号会重新排序,并且根据行的奇偶性,为行设置不同的背景颜色,以增强可读性。
效果展示
点击删除按钮会删除对应的数据,并且后续的数据继续替补上一条数据的序号,但内容不变,颜色仍然是偶数行
代码部分
HTML结构
这里定义了一个基本的 HTML 页面结构,包含一个具有特定样式的表格。thead
部分定义了表头,tbody
部分初始有一行数据,并且页面底部有一个用于添加行的按钮。
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title></title><style type="text/css">#tablee {border-collapse: collapse;text-align: center;}td {border-style: solid;border-width: 1px;padding: 5px;}</style>
</head><body><table id="tablee" cellspacing="" cellpadding=""><thead><tr><td>序号</td><td>内容</td><td>操作</td></tr></thead><tbody><tr><td>1</td><td>100</td><td><button id="deleteButton">删除</button></td></tr></tbody></table><button id="addButton">添加行</button>
CSS部分
#tablee {border-collapse: collapse;text-align: center;
}td {border-style: solid;border-width: 1px;padding: 5px;
}
JS部分
1.变量声明及初始化
addButton
:通过getElementById
方法获取页面中的 “添加行” 按钮元素,以便后续为其添加点击事件。tbody
:使用querySelector
获取表格的tbody
元素,后续对表格行的添加、删除操作都将围绕这个元素进行。data
:初始化一个数据变量,用于生成 “内容” 列的数据,初始值为 100。
// 获取添加行按钮,为了添加事件
let addButton = document.getElementById('addButton');
// 获取tbody,为了进行一系列操作(对他的子集)
let tbody = document.querySelector('tbody');
// 声明数据 这是初始化的数据 从100开始
let data = 100;
2.添加行功能
- 事件绑定:为
addButton
添加click
事件监听器,当按钮被点击时,执行回调函数。 - 创建新行及单元格:
- 使用
document.createElement('tr')
创建一个新的表格行元素newTr
。 - 依次创建三个
td
单元格,分别用于显示序号(numTd
)、内容(contentTd
)和操作按钮(operateTd
)。
- 使用
- 填充单元格内容:
- 对 “内容” 单元格
contentTd
,先将data
的值增加 100,然后将新值写入contentTd
的innerHTML
。 - 为 “操作” 单元格
operateTd
创建一个 “删除” 按钮tdButton
,并将按钮添加到该单元格。
- 对 “内容” 单元格
- 添加新行到表格:将包含三个单元格的
newTr
行添加到tbody
中。 - 更新序号和颜色:调用
setNumberAndColor
函数,对表格的序号和行背景颜色进行更新。
// 给添加按键添加事件
addButton.onclick = function () {// 1.创建一个新的tr行let newTr = document.createElement('tr');// 2.创建第一个单元格let numTd = document.createElement('td');// 2.1.把第一个td插到Tr里newTr.appendChild(numTd)// 3.创建第二个单元格let contentTd = document.createElement('td');// 3.1.data值加100后,重新赋值给datadata += 100;// 3.2.往contentTd中写入data数据contentTd.innerHTML = data;// 3.3.把contentTd插入到newTrnewTr.appendChild(contentTd);// 4.创建第三个单元格 // 并且第三个单元格里只存放按钮let operateTd = document.createElement('td');// 5.创建删除按钮let tdButton = document.createElement('button');// 5.1 按钮名字改成删除tdButton.innerHTML = '删除'// 5.2把删除按钮插回创建的第三个单元格operateTd.appendChild(tdButton);// 5.3把第三个单元格插到tr行里newTr.appendChild(operateTd);// 5.4最后把newTr行插入到tbody中tbody.appendChild(newTr);// 调用一下封装好的函数 (序号和颜色)setNumberAndColor();
};
3.颜色及序号
- 获取所有行:使用
document.querySelectorAll('tr')
获取页面中所有的tr
元素,存储在allTr
数组中。 - 遍历并设置样式和序号:
- 从索引 1 开始遍历
allTr
数组(跳过表头行)。 - 根据索引
i
的奇偶性,为每行设置不同的背景颜色。如果i
能被 2 整除,背景颜色设为黄色;否则设为白色。 - 通过
querySelector('td:first-child')
获取每行的第一个td
单元格,将其内容设置为当前的行索引i
,从而实现序号的更新。
- 从索引 1 开始遍历
function setNumberAndColor() {// let setNumber = 1;let allTr = document.querySelectorAll('tr'); /*获取所有tr标签 */// 遍历获取到的所有tr// 从下标1开始,并且小于 < 获取到的所有tr的长度for (let i = 1; i < allTr.length; i++) {// 判断是否为偶数if (i % 2 == 0) {// 满足被2整除的 就加一个背景颜色allTr[i].style.backgroundColor = 'yellow';} else {// 否则 就是白色allTr[i].style.backgroundColor = 'white';}// 获取当前tr行 里的第一个td格:用于显示序列号的单元格let getShowNumber = allTr[i].querySelector('td:first-child');if (getShowNumber) {// ↑↑满足条件的话, ↓↓就把序号添加到 获取的tr行里getShowNumber.innerHTML = i; // 设置序号// 序号增加1,往后递增// setNumber++; // 序号递增}}
}
4.删除行功能
- 事件委托:在
tbody
元素上添加click
事件监听器。这样当点击tbody
内的任何元素时,都会触发该事件。 - 判断点击目标:获取事件的目标元素
targetElement
,检查其innerHTML
是否为 “删除”。如果是,则表示点击了 “删除” 按钮。 - 删除对应行:通过
parentElement
两次获取到按钮所在的tr
行元素,然后使用tbody.removeChild(tr)
将该行从tbody
中移除。 - 更新序号和颜色:调用
setNumberAndColor
函数,确保删除行后表格的序号和背景颜色依然保持正确的显示。
// 删除按钮
// 用的是 事件监听,因为要确定我在页面上 点的是哪一个元素
tbody.addEventListener('click', function (e) {let targetElement = e.targetif (targetElement.innerHTML == '删除') {// 获取到当前元素的父级的父级(他爷爷)let tr = targetElement.parentElement.parentElement;// 从tbody中删除对应的trtbody.removeChild(tr);// 调用封装的函数(序号颜色) // 这里调用是因为防止删除白色行时,下面的黄色行自动向上顶,导致邻近的两行都是黄色setNumberAndColor();}
})
各行换色思路二:利用字符串拼接
(大致思路一致,不过多赘述)
代码展示
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title></title><style type="text/css">#tablee {border-collapse: collapse;text-align: center;}td {border-style: solid;border-width: 1px;padding: 5px;}</style></head><body><table id="tablee" cellspacing="" cellpadding=""><thead><tr><td>序号</td><td>内容</td><td>操作</td></tr></thead><tbody></tbody></table><!-- 这个思路类似于:一个城市模块化积木,每个区域有需要新添/放置设施(添加行tr)把积木在一个底座上一个一个拼起来 然后塞到相应的区域(底座:空字符串;积木:被拼接的td;区域:tbody)因为城市的区域很大 不可能每个地方都重新造设施(改变/添加新的数据) 所以用同一个拼好的积木模板(封装好的函数) 往区域里放置(调用函数)但是有时候想对区域里某个地方的模板进行修改时 就需要把新添的内容放到模板上(添加数据后,数据发生改变就重新调用渲染函数往里面传参数)--><button id="addButton" type="button">添加行</button><script type="text/javascript">/* 声明一个数组(开始显示的数据) */let data = [100];/* 内容格里的值 */let content = 100;/* 初始化每次点击,添加按钮的初始值 */let addNumber=100;/* 封装渲染函数 */function xuanran(data) {/* 拼接字符串,为了拼接tr行的字符串 */let str = '';/* 循环data,循环几次 就拼接几次 */for (let i = 1; i < data.length; i++) {/* 拼接字符串,将后续拼接的内容添加到str里 *//* 用三元去加颜色 */str += '<tr style=" background-color:' + (i % 2 == 0 ? 'pink' : '') + ';">' +/* 1.拼接第一个单元格(序号) */'<td>' + i + '</td>' +/* 2.拼接第二个单元格(内容,内容上面已经给了 从100开始) */'<td>' + data[i] + '</td>' +/* 3.拼接第三个单元格(删除按钮)并设置了自定义属性data_index并给了一个i的值为下标,用来确定按钮对应的是哪一行*/'<td><button data_index="' + i + '">删除</button></td>' +'</tr>'}/* 上面拼接完之后 并不在页面上需要往tbody里塞*/let getBody = document.querySelector('tbody');/* 把拼接好的字符串写入tbody里 */getBody.innerHTML = str;};/* 把conten放入data里 */data.push(content);/* 每次添加一行content都+100 */content += 100;/* 上面一行数据发生变化了,所以调用渲染函数(传一下新的参) */xuanran(data);// 删除按钮的事件监听 /* 1.先获取一下tbody */let tbody = document.querySelector('tbody');/* 2.然后对tbody监听。添加click点击事件监听。e是一个事件对象包含了 与该点击事件相关的各种信息 */tbody.addEventListener('click', function(e) {/* 3.判断点击的是否为删除 e.target 是事件对象 e 的一个属性 ,它指向谁被点击了通过e.target.innerHTML去获取被点击的文本内容是否满足条件*/if (e.target.innerHTML == '删除') {/* 4.获取要删除数据的索引 *//* getAttribute 是 DOM 元素的一个方法,用于获取指定元素的某个属性的值 *//* 通过e.target来确定谁被点了 然后获取它的下标 */let getIndex = e.target.getAttribut e('data_index');/* 5.从数组中删除对应的元素 *//* 删除获取的getIndex,删除1个元素(,逗号前是已经获取到的元素的对应下标,后是要删除的元素个数) */data.splice(getIndex, 1);/* 因为值变化了 所以重新调用渲染一下,把新参数传进去 */xuanran(data);}});// 添加行按钮 /* 1.先获取添加按钮 */let addDeleteButto = document.getElementById('addButton');/* 2.给添加按钮上点击事件监听 */addDeleteButto.addEventListener('click', function() {/* 每点击一次添加 内容数值都加100,最上面写了初始值 */addNumber += 100;/* 把每次叠加的值插入到data里 */data.push(addNumber);/* 因为数变化了 ,所以重新渲染传参 */xuanran(data); });</script></body>
</html>
区别部分
- 对于数组中的每一项,拼接一个完整的
<tr>
元素字符串。- 背景颜色设置:使用三元运算符
(i % 2 == 0 ? 'pink' : '')
根据当前行的索引i
的奇偶性来设置背景颜色。如果i
是偶数,背景颜色为pink
;否则不设置背景颜色。 - 序号单元格:第一个
<td>
元素显示当前行的序号i
。 - 内容单元格:第二个
<td>
元素显示data[i]
的值,即当前数组项的数据。 - 删除按钮单元格:第三个
<td>
元素包含一个<button>
元素,按钮上设置了自定义属性data_index
,其值为当前行的索引i
,用于后续识别要删除的行。
- 背景颜色设置:使用三元运算符
/* 封装渲染函数 */function xuanran(data) {/* 拼接字符串,为了拼接tr行的字符串 */let str = '';/* 循环data,循环几次 就拼接几次 */for (let i = 1; i < data.length; i++) {/* 拼接字符串,将后续拼接的内容添加到str里 *//* 用三元去加颜色 */str += '<tr style=" background-color:' + (i % 2 == 0 ? 'pink' : '') + ';">' +/* 1.拼接第一个单元格(序号) */'<td>' + i + '</td>' +/* 2.拼接第二个单元格(内容,内容上面已经给了 从100开始) */'<td>' + data[i] + '</td>' +/* 3.拼接第三个单元格(删除按钮)并设置了自定义属性data_index并给了一个i的值为下标,用来确定按钮对应的是哪一行*/'<td><button data_index="' + i + '">删除</button></td>' +'</tr>'}/* 上面拼接完之后 并不在页面上需要往tbody里塞*/let getBody = document.querySelector('tbody');/* 把拼接好的字符串写入tbody里 */getBody.innerHTML = str;};