Skip to content

2.5 表格元素

关键词: HTML5表格元素, table标签, 表格结构, 数据表格, 表格样式, 响应式表格, 表格可访问性, thead, tbody, tfoot

学习目标

  • 掌握HTML5表格元素的基本结构和语法
  • 理解表格的语义化和可访问性原则
  • 学会创建复杂的数据表格和跨行跨列
  • 掌握表格样式设计和响应式处理
  • 了解表格的最佳实践和性能优化

本节概述

表格是HTML中用于展示结构化数据的重要元素。虽然在早期的Web开发中表格常被滥用于布局,但在HTML5中,表格应该严格用于展示表格数据。正确使用表格元素不仅能够清晰地展示数据,还能提供良好的可访问性和语义化结构。本节将详细介绍HTML5表格元素的使用方法、样式技巧和最佳实践。

2.5.1 基本表格结构

核心表格元素

html
<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)

简单表格示例

html
<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>

2.5.2 表格结构元素

<thead><tbody><tfoot>

html
<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> 表格标题

html
<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> 列分组

html
<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>

2.5.3 单元格跨行跨列

colspan 属性(跨列)

html
<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>

rowspan 属性(跨行)

html
<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>

复杂跨行跨列示例

html
<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>

2.5.4 表格样式

基本表格样式

css
/* 基础表格样式 */
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;
}

现代表格样式

css
.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;
}

紧凑型表格

css
.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;
}

2.5.5 响应式表格

水平滚动方式

html
<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>
css
.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;
  }
}

堆叠布局方式

css
@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;
  }
}
html
<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>

卡片式布局

css
@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;
  }
}

2.5.6 表格无障碍访问

表头关联

html
<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>

复杂表头关联

html
<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>

scope 属性

html
<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>

表格摘要

html
<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>

2.5.7 实际应用示例

数据表格

html
<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>

财务报表

html
<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>

产品比较表

html
<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>

2.5.8 可排序表格

JavaScript实现排序

html
<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>
javascript
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 = '↕';
    }
  });
}

2.5.9 表格性能优化

虚拟滚动

javascript
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('');
  }
}

分页表格

html
<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>
javascript
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;
  }
}

2.5.10 常见错误和最佳实践

常见错误

  1. 滥用表格进行布局
html
<!-- 错误:使用表格进行页面布局 -->
<table>
  <tr>
    <td>导航菜单</td>
    <td>主要内容</td>
    <td>侧边栏</td>
  </tr>
</table>

<!-- 正确:使用CSS进行布局 -->
<div class="layout">
  <nav>导航菜单</nav>
  <main>主要内容</main>
  <aside>侧边栏</aside>
</div>
  1. 缺少表头
html
<!-- 错误:没有表头 -->
<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>
  1. 忽略无障碍访问
html
<!-- 错误:没有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>

最佳实践

  1. 仅用于表格数据

    • 表格应该只用于展示表格数据
    • 不要用表格进行页面布局
    • 考虑数据的结构化特性
  2. 提供清晰的表头

    • 使用<th>元素作为表头
    • 添加适当的scope属性
    • 为复杂表格提供headers属性
  3. 确保响应式设计

    • 在移动设备上考虑表格的显示方式
    • 使用水平滚动或堆叠布局
    • 保持重要信息的可见性
  4. 优化性能

    • 对大量数据使用分页或虚拟滚动
    • 避免在表格中放置大量图片
    • 使用CSS而非内联样式
  5. 注意无障碍访问

    • 提供表格标题(<caption>
    • 使用适当的标记关联表头和数据
    • 确保键盘导航的可用性

小结

表格是展示结构化数据的重要工具,正确使用表格元素能够提供清晰的数据展示和良好的用户体验。在实际开发中,要注意表格的语义化、响应式设计、无障碍访问和性能优化。避免滥用表格进行布局,专注于其数据展示的核心功能。

下一节我们将学习表单元素,了解如何创建交互式的用户输入界面。