Search K
Appearance
Appearance
关键词: HTML5表格元素, table标签, 表格结构, 数据表格, 表格样式, 响应式表格, 表格可访问性, thead, tbody, tfoot
表格是HTML中用于展示结构化数据的重要元素。虽然在早期的Web开发中表格常被滥用于布局,但在HTML5中,表格应该严格用于展示表格数据。正确使用表格元素不仅能够清晰地展示数据,还能提供良好的可访问性和语义化结构。本节将详细介绍HTML5表格元素的使用方法、样式技巧和最佳实践。
<table>
<tr>
<th>表头1</th>
<th>表头2</th>
<th>表头3</th>
</tr>
<tr>
<td>数据1</td>
<td>数据2</td>
<td>数据3</td>
</tr>
<tr>
<td>数据4</td>
<td>数据5</td>
<td>数据6</td>
</tr>
</table><table> - 表格容器<tr> - 表格行(table row)<th> - 表头单元格(table header)<td> - 数据单元格(table data)<table>
<tr>
<th>姓名</th>
<th>年龄</th>
<th>职业</th>
</tr>
<tr>
<td>张三</td>
<td>28</td>
<td>工程师</td>
</tr>
<tr>
<td>李四</td>
<td>32</td>
<td>设计师</td>
</tr>
<tr>
<td>王五</td>
<td>25</td>
<td>产品经理</td>
</tr>
</table><thead>、<tbody>、<tfoot> <table>
<thead>
<tr>
<th>产品</th>
<th>价格</th>
<th>数量</th>
<th>小计</th>
</tr>
</thead>
<tbody>
<tr>
<td>笔记本电脑</td>
<td>¥5,999</td>
<td>2</td>
<td>¥11,998</td>
</tr>
<tr>
<td>无线鼠标</td>
<td>¥299</td>
<td>1</td>
<td>¥299</td>
</tr>
<tr>
<td>机械键盘</td>
<td>¥899</td>
<td>1</td>
<td>¥899</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="3">总计</td>
<td>¥13,196</td>
</tr>
</tfoot>
</table><caption> 表格标题 <table>
<caption>2023年第一季度销售数据</caption>
<thead>
<tr>
<th>月份</th>
<th>销售额</th>
<th>增长率</th>
</tr>
</thead>
<tbody>
<tr>
<td>1月</td>
<td>¥100,000</td>
<td>+5%</td>
</tr>
<tr>
<td>2月</td>
<td>¥120,000</td>
<td>+20%</td>
</tr>
<tr>
<td>3月</td>
<td>¥130,000</td>
<td>+8.3%</td>
</tr>
</tbody>
</table><colgroup> 和 <col> 列分组 <table>
<colgroup>
<col style="background-color: #f0f0f0;">
<col span="2" style="background-color: #e0e0e0;">
<col style="background-color: #d0d0d0;">
</colgroup>
<thead>
<tr>
<th>项目</th>
<th>Q1</th>
<th>Q2</th>
<th>总计</th>
</tr>
</thead>
<tbody>
<tr>
<td>收入</td>
<td>500万</td>
<td>600万</td>
<td>1100万</td>
</tr>
<tr>
<td>支出</td>
<td>300万</td>
<td>400万</td>
<td>700万</td>
</tr>
</tbody>
</table><table>
<tr>
<th colspan="3">产品信息</th>
</tr>
<tr>
<th>名称</th>
<th>价格</th>
<th>库存</th>
</tr>
<tr>
<td>iPhone 15</td>
<td>¥5,999</td>
<td>100</td>
</tr>
<tr>
<td colspan="2">总价值</td>
<td>¥599,900</td>
</tr>
</table><table>
<tr>
<th rowspan="2">类别</th>
<th colspan="2">数量</th>
</tr>
<tr>
<th>上半年</th>
<th>下半年</th>
</tr>
<tr>
<td>手机</td>
<td>1000</td>
<td>1200</td>
</tr>
<tr>
<td>平板</td>
<td>300</td>
<td>400</td>
</tr>
</table><table>
<caption>销售业绩表</caption>
<thead>
<tr>
<th rowspan="2">销售员</th>
<th colspan="2">第一季度</th>
<th colspan="2">第二季度</th>
<th rowspan="2">总计</th>
</tr>
<tr>
<th>目标</th>
<th>实际</th>
<th>目标</th>
<th>实际</th>
</tr>
</thead>
<tbody>
<tr>
<td>张三</td>
<td>100万</td>
<td>120万</td>
<td>110万</td>
<td>130万</td>
<td>250万</td>
</tr>
<tr>
<td>李四</td>
<td>80万</td>
<td>90万</td>
<td>85万</td>
<td>95万</td>
<td>185万</td>
</tr>
</tbody>
</table>/* 基础表格样式 */
table {
border-collapse: collapse;
width: 100%;
margin: 20px 0;
font-family: Arial, sans-serif;
}
th, td {
border: 1px solid #ddd;
padding: 12px;
text-align: left;
}
th {
background-color: #f2f2f2;
font-weight: bold;
}
/* 斑马纹效果 */
tbody tr:nth-child(even) {
background-color: #f9f9f9;
}
/* 悬停效果 */
tbody tr:hover {
background-color: #f5f5f5;
}.modern-table {
border-collapse: collapse;
width: 100%;
background-color: white;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
border-radius: 8px;
overflow: hidden;
}
.modern-table th {
background-color: #4CAF50;
color: white;
font-weight: 600;
padding: 15px;
text-align: left;
border: none;
}
.modern-table td {
padding: 15px;
border: none;
border-bottom: 1px solid #eee;
}
.modern-table tbody tr:last-child td {
border-bottom: none;
}
.modern-table tbody tr:hover {
background-color: #f8f9fa;
}.compact-table {
border-collapse: collapse;
width: 100%;
font-size: 14px;
}
.compact-table th,
.compact-table td {
border: 1px solid #ddd;
padding: 6px 8px;
}
.compact-table th {
background-color: #f0f0f0;
font-weight: 600;
}<div class="table-container">
<table class="responsive-table">
<thead>
<tr>
<th>编号</th>
<th>姓名</th>
<th>邮箱</th>
<th>电话</th>
<th>地址</th>
<th>职位</th>
<th>部门</th>
<th>入职日期</th>
</tr>
</thead>
<tbody>
<tr>
<td>001</td>
<td>张三</td>
<td>zhangsan@example.com</td>
<td>138-0000-0000</td>
<td>北京市朝阳区</td>
<td>工程师</td>
<td>技术部</td>
<td>2023-01-15</td>
</tr>
</tbody>
</table>
</div>.table-container {
overflow-x: auto;
margin: 20px 0;
}
.responsive-table {
min-width: 800px;
border-collapse: collapse;
width: 100%;
}
@media screen and (max-width: 768px) {
.table-container {
font-size: 14px;
}
}@media screen and (max-width: 768px) {
.stack-table,
.stack-table thead,
.stack-table tbody,
.stack-table th,
.stack-table td,
.stack-table tr {
display: block;
}
.stack-table thead tr {
position: absolute;
top: -9999px;
left: -9999px;
}
.stack-table tr {
border: 1px solid #ccc;
margin-bottom: 10px;
padding: 10px;
}
.stack-table td {
border: none;
border-bottom: 1px solid #eee;
position: relative;
padding-left: 50%;
}
.stack-table td:before {
content: attr(data-label) ": ";
position: absolute;
left: 6px;
width: 45%;
padding-right: 10px;
white-space: nowrap;
font-weight: bold;
}
}<table class="stack-table">
<thead>
<tr>
<th>姓名</th>
<th>邮箱</th>
<th>电话</th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="姓名">张三</td>
<td data-label="邮箱">zhangsan@example.com</td>
<td data-label="电话">138-0000-0000</td>
</tr>
</tbody>
</table>@media screen and (max-width: 768px) {
.card-table {
display: none;
}
.card-layout {
display: block;
}
.card-item {
background: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.card-item h3 {
margin: 0 0 15px 0;
color: #333;
}
.card-item .info {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.card-item .label {
font-weight: bold;
color: #666;
}
}<table>
<thead>
<tr>
<th id="name">姓名</th>
<th id="email">邮箱</th>
<th id="phone">电话</th>
</tr>
</thead>
<tbody>
<tr>
<td headers="name">张三</td>
<td headers="email">zhangsan@example.com</td>
<td headers="phone">138-0000-0000</td>
</tr>
</tbody>
</table><table>
<thead>
<tr>
<th rowspan="2" id="employee">员工</th>
<th colspan="2" id="q1">第一季度</th>
<th colspan="2" id="q2">第二季度</th>
</tr>
<tr>
<th id="q1-target">目标</th>
<th id="q1-actual">实际</th>
<th id="q2-target">目标</th>
<th id="q2-actual">实际</th>
</tr>
</thead>
<tbody>
<tr>
<td headers="employee">张三</td>
<td headers="q1 q1-target">100万</td>
<td headers="q1 q1-actual">120万</td>
<td headers="q2 q2-target">110万</td>
<td headers="q2 q2-actual">130万</td>
</tr>
</tbody>
</table><table>
<thead>
<tr>
<th scope="col">产品</th>
<th scope="col">价格</th>
<th scope="col">库存</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">iPhone 15</th>
<td>¥5,999</td>
<td>100</td>
</tr>
<tr>
<th scope="row">iPad Pro</th>
<td>¥6,199</td>
<td>50</td>
</tr>
</tbody>
</table><table summary="这是一个包含学生成绩信息的表格,包括姓名、语文、数学、英语和总分">
<caption>2023年期末考试成绩</caption>
<thead>
<tr>
<th scope="col">姓名</th>
<th scope="col">语文</th>
<th scope="col">数学</th>
<th scope="col">英语</th>
<th scope="col">总分</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">张三</th>
<td>85</td>
<td>92</td>
<td>88</td>
<td>265</td>
</tr>
</tbody>
</table><div class="data-table-container">
<table class="data-table">
<caption>公司员工信息表</caption>
<thead>
<tr>
<th scope="col">员工ID</th>
<th scope="col">姓名</th>
<th scope="col">部门</th>
<th scope="col">职位</th>
<th scope="col">入职日期</th>
<th scope="col">薪资</th>
<th scope="col">操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>001</td>
<td>张三</td>
<td>技术部</td>
<td>高级工程师</td>
<td>2023-01-15</td>
<td>¥15,000</td>
<td>
<button type="button" class="btn-edit">编辑</button>
<button type="button" class="btn-delete">删除</button>
</td>
</tr>
<tr>
<td>002</td>
<td>李四</td>
<td>设计部</td>
<td>UI设计师</td>
<td>2023-02-01</td>
<td>¥12,000</td>
<td>
<button type="button" class="btn-edit">编辑</button>
<button type="button" class="btn-delete">删除</button>
</td>
</tr>
</tbody>
</table>
</div><table class="financial-table">
<caption>2023年财务报表</caption>
<thead>
<tr>
<th scope="col">项目</th>
<th scope="col">第一季度</th>
<th scope="col">第二季度</th>
<th scope="col">第三季度</th>
<th scope="col">第四季度</th>
<th scope="col">全年总计</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">营业收入</th>
<td>1,200万</td>
<td>1,500万</td>
<td>1,800万</td>
<td>2,000万</td>
<td>6,500万</td>
</tr>
<tr>
<th scope="row">营业成本</th>
<td>800万</td>
<td>1,000万</td>
<td>1,200万</td>
<td>1,300万</td>
<td>4,300万</td>
</tr>
<tr>
<th scope="row">毛利润</th>
<td>400万</td>
<td>500万</td>
<td>600万</td>
<td>700万</td>
<td>2,200万</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row">利润率</th>
<td>33.3%</td>
<td>33.3%</td>
<td>33.3%</td>
<td>35.0%</td>
<td>33.8%</td>
</tr>
</tfoot>
</table><table class="comparison-table">
<caption>产品功能对比</caption>
<thead>
<tr>
<th scope="col">功能</th>
<th scope="col">基础版</th>
<th scope="col">专业版</th>
<th scope="col">企业版</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">用户数量</th>
<td>最多5个</td>
<td>最多50个</td>
<td>无限制</td>
</tr>
<tr>
<th scope="row">存储空间</th>
<td>10GB</td>
<td>100GB</td>
<td>1TB</td>
</tr>
<tr>
<th scope="row">技术支持</th>
<td>邮件支持</td>
<td>电话+邮件</td>
<td>24/7专属客服</td>
</tr>
<tr>
<th scope="row">价格</th>
<td>免费</td>
<td>¥99/月</td>
<td>¥299/月</td>
</tr>
</tbody>
</table><table class="sortable-table">
<thead>
<tr>
<th onclick="sortTable(0)">姓名 <span class="sort-arrow">↕</span></th>
<th onclick="sortTable(1)">年龄 <span class="sort-arrow">↕</span></th>
<th onclick="sortTable(2)">薪资 <span class="sort-arrow">↕</span></th>
</tr>
</thead>
<tbody id="table-body">
<tr>
<td>张三</td>
<td>28</td>
<td>15000</td>
</tr>
<tr>
<td>李四</td>
<td>32</td>
<td>18000</td>
</tr>
<tr>
<td>王五</td>
<td>25</td>
<td>12000</td>
</tr>
</tbody>
</table>function sortTable(n) {
const table = document.querySelector('.sortable-table');
const tbody = table.querySelector('tbody');
const rows = Array.from(tbody.querySelectorAll('tr'));
// 确定排序方向
const currentDirection = table.dataset.sortDirection || 'asc';
const newDirection = currentDirection === 'asc' ? 'desc' : 'asc';
table.dataset.sortDirection = newDirection;
// 排序行
rows.sort((a, b) => {
const aValue = a.cells[n].textContent.trim();
const bValue = b.cells[n].textContent.trim();
// 检查是否为数字
const aNum = parseFloat(aValue);
const bNum = parseFloat(bValue);
if (!isNaN(aNum) && !isNaN(bNum)) {
return newDirection === 'asc' ? aNum - bNum : bNum - aNum;
} else {
return newDirection === 'asc'
? aValue.localeCompare(bValue)
: bValue.localeCompare(aValue);
}
});
// 重新添加排序后的行
rows.forEach(row => tbody.appendChild(row));
// 更新排序箭头
updateSortArrows(table, n, newDirection);
}
function updateSortArrows(table, columnIndex, direction) {
const arrows = table.querySelectorAll('.sort-arrow');
arrows.forEach((arrow, index) => {
if (index === columnIndex) {
arrow.textContent = direction === 'asc' ? '↑' : '↓';
} else {
arrow.textContent = '↕';
}
});
}class VirtualTable {
constructor(container, data, rowHeight = 40) {
this.container = container;
this.data = data;
this.rowHeight = rowHeight;
this.visibleRows = Math.ceil(container.clientHeight / rowHeight);
this.startIndex = 0;
this.endIndex = this.visibleRows;
this.init();
}
init() {
this.createTable();
this.bindEvents();
this.render();
}
createTable() {
this.container.innerHTML = `
<div class="virtual-table">
<div class="virtual-table-header">
<table>
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>邮箱</th>
<th>部门</th>
</tr>
</thead>
</table>
</div>
<div class="virtual-table-body">
<div class="virtual-table-spacer" style="height: ${this.data.length * this.rowHeight}px;"></div>
<table>
<tbody></tbody>
</table>
</div>
</div>
`;
}
bindEvents() {
const tableBody = this.container.querySelector('.virtual-table-body');
tableBody.addEventListener('scroll', () => {
this.handleScroll();
});
}
handleScroll() {
const scrollTop = this.container.querySelector('.virtual-table-body').scrollTop;
const newStartIndex = Math.floor(scrollTop / this.rowHeight);
const newEndIndex = Math.min(newStartIndex + this.visibleRows, this.data.length);
if (newStartIndex !== this.startIndex || newEndIndex !== this.endIndex) {
this.startIndex = newStartIndex;
this.endIndex = newEndIndex;
this.render();
}
}
render() {
const tbody = this.container.querySelector('tbody');
const visibleData = this.data.slice(this.startIndex, this.endIndex);
tbody.innerHTML = visibleData.map((item, index) => `
<tr style="transform: translateY(${(this.startIndex + index) * this.rowHeight}px);">
<td>${item.id}</td>
<td>${item.name}</td>
<td>${item.email}</td>
<td>${item.department}</td>
</tr>
`).join('');
}
}<div class="paginated-table">
<table class="data-table">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>邮箱</th>
<th>部门</th>
</tr>
</thead>
<tbody id="table-data">
<!-- 数据将通过JavaScript动态加载 -->
</tbody>
</table>
<div class="pagination">
<button id="prev-page" disabled>上一页</button>
<span id="page-info">第1页,共10页</span>
<button id="next-page">下一页</button>
</div>
</div>class PaginatedTable {
constructor(data, pageSize = 10) {
this.data = data;
this.pageSize = pageSize;
this.currentPage = 1;
this.totalPages = Math.ceil(data.length / pageSize);
this.bindEvents();
this.renderPage();
}
bindEvents() {
document.getElementById('prev-page').addEventListener('click', () => {
if (this.currentPage > 1) {
this.currentPage--;
this.renderPage();
}
});
document.getElementById('next-page').addEventListener('click', () => {
if (this.currentPage < this.totalPages) {
this.currentPage++;
this.renderPage();
}
});
}
renderPage() {
const startIndex = (this.currentPage - 1) * this.pageSize;
const endIndex = startIndex + this.pageSize;
const pageData = this.data.slice(startIndex, endIndex);
// 渲染表格数据
const tbody = document.getElementById('table-data');
tbody.innerHTML = pageData.map(item => `
<tr>
<td>${item.id}</td>
<td>${item.name}</td>
<td>${item.email}</td>
<td>${item.department}</td>
</tr>
`).join('');
// 更新分页信息
document.getElementById('page-info').textContent =
`第${this.currentPage}页,共${this.totalPages}页`;
// 更新按钮状态
document.getElementById('prev-page').disabled = this.currentPage === 1;
document.getElementById('next-page').disabled = this.currentPage === this.totalPages;
}
}<!-- 错误:使用表格进行页面布局 -->
<table>
<tr>
<td>导航菜单</td>
<td>主要内容</td>
<td>侧边栏</td>
</tr>
</table>
<!-- 正确:使用CSS进行布局 -->
<div class="layout">
<nav>导航菜单</nav>
<main>主要内容</main>
<aside>侧边栏</aside>
</div><!-- 错误:没有表头 -->
<table>
<tr>
<td>张三</td>
<td>28</td>
<td>工程师</td>
</tr>
</table>
<!-- 正确:包含表头 -->
<table>
<tr>
<th>姓名</th>
<th>年龄</th>
<th>职业</th>
</tr>
<tr>
<td>张三</td>
<td>28</td>
<td>工程师</td>
</tr>
</table><!-- 错误:没有scope属性 -->
<table>
<tr>
<th>姓名</th>
<th>年龄</th>
</tr>
<tr>
<td>张三</td>
<td>28</td>
</tr>
</table>
<!-- 正确:添加scope属性 -->
<table>
<tr>
<th scope="col">姓名</th>
<th scope="col">年龄</th>
</tr>
<tr>
<td>张三</td>
<td>28</td>
</tr>
</table>仅用于表格数据
提供清晰的表头
<th>元素作为表头scope属性headers属性确保响应式设计
优化性能
注意无障碍访问
<caption>)表格是展示结构化数据的重要工具,正确使用表格元素能够提供清晰的数据展示和良好的用户体验。在实际开发中,要注意表格的语义化、响应式设计、无障碍访问和性能优化。避免滥用表格进行布局,专注于其数据展示的核心功能。
下一节我们将学习表单元素,了解如何创建交互式的用户输入界面。