简介这份HTML表格制作练习包面向网页设计初学者与前端入门教学场景围绕简单表格与表单页面的搭建提供可直接参考的源码与配套素材帮助读者理解表格结构、行列组织及基础页面布局。压缩包共5个文件约30KB以1个HTML页面源码为主辅以JPG效果图与图片素材并附Markdown说明便于对照页面呈现与代码结构进行学习。已有203人学习下载适合课堂示例、课后练习或自学临摹。通过页面源码可以观察标签嵌套、表格样式与图文配合方式效果图有助于核对最终呈现说明文档则补充使用提示与结构信息图片素材还能直接用于本地调试减少额外找图成本。整体体量轻巧、层次清晰适合需要快速上手HTML表格与表单基础练习的读者参考。1. 一份 HTML 表格源码包真正值钱的是那几行 tbody拿到一个叫「HTML 制作简单表格源码」的压缩包不少人的第一反应是解压、双击首页文件、看一眼效果然后把整段表格标签复制进自己的页面。这么做能跑但过几天要加一列、要对齐金额、要在手机上不撑破屏幕时你会发现当初复制的那份 HTML 表格源码里真正有用的并不是那几行tr和td而是外面那层thead/tbody的分组、caption的说明以及样式里对border-collapse和table-layout的处理。这份资源适合刚接触页面结构、需要快速交出一个可维护表格的人也适合已经写过表格但被合并单元格和自适应宽度反复折腾的熟手。它解决的是一件事让表格从“能看”变成“改得动”。2. 表格骨架caption、thead、tbody 各管什么表格标签谁都会写但把分组标签用对的人不多。分组不是形式主义它决定了三件事屏幕阅读器怎么念、打印时表头能不能重复、脚本按行取值时会不会把表头算进数据里。这一章先把骨架搭稳后面的样式和合并才有落脚点。2.1 只写 tr 和 td 会在哪里翻车最省事的写法是table里直接排一串trtd浏览器照样渲染肉眼也看不出毛病。问题出在后续维护当你想给表头单独加底色时只能靠tr:first-child去猜当你想让表头在打印时每页重复没有thead就没有抓手当脚本要遍历数据行时表头行会混在querySelectorAll(tr)的结果里得多写一层判断把它剔掉。这些都不是玄学是结构缺失带来的连锁反应。更隐蔽的是可访问性。屏幕阅读器读到一张没有th的表格时只能逐个念单元格内容用户听到“2024、一季度、12.5”却不知道哪个是年份、哪个是季度。补上thead和th之后读屏软件才能把列标题和单元格关联起来。这类问题在内部系统里没人提一旦交付给需要无障碍支持的场景返工成本比当初多写四行标签高得多。所以判断一份表格源码值不值得留先看它有没有把表头、表体、表尾分开。分开之后样式选择器有明确目标脚本取值有明确边界打印和读屏也各有各的入口。2.2 一份可直接跑的最小骨架下面这段是压缩包里最该先看的部分也是我一般会保留并改写的基础结构。它把说明、表头、表体、表尾四块都摆出来了去掉任何一块都还能显示但会丢掉对应的能力。!-- 表格根节点caption 必须作为第一个子元素出现 -- table classdata-table !-- 表格说明读屏会先念这句打印时也可作为标题 -- caption2024 年各区域季度销售额单位万元/caption !-- 表头分组打印时 thead 默认在每页顶部重复 -- thead tr !-- scopecol 声明这是列标题读屏据此关联下方单元格 -- th scopecol区域/th th scopecol一季度/th th scopecol二季度/th th scopecol合计/th /tr /thead !-- 表体分组数据行全部放这里脚本只遍历这个范围 -- tbody tr th scoperow华东/th !-- 行标题用 th scoperow -- td128.4/td td143.9/td td272.3/td /tr tr th scoperow华南/th td96.2/td td101.7/td td197.9/td /tr /tbody !-- 表尾分组放汇总行和 tbody 的数据行区分开 -- tfoot tr th scoperow合计/th td224.6/td td245.6/td td470.2/td /tr /tfoot /table逻辑上要留意两点。第一caption必须是table的第一个子元素放到thead后面浏览器仍会渲染但部分读屏软件的朗读顺序会乱。第二tfoot在 HTML 规范里可以写在tbody之前浏览器渲染时也会把它放到表尾但源码顺序和视觉顺序不一致会让接手的人多花几分钟确认所以我一般按视觉顺序写。参数方面scope只有四个常用取值col、row、colgroup、rowgroup。单层表头用col左侧行标题用row多层表头才需要colgroup。caption不需要额外属性想控制位置用 CSS 的caption-side取值top或bottom。2.3 列宽控制的三个入口列宽是表格里最容易打架的地方。控制入口有三个优先级从低到高分别是内容自然宽度、col上的宽度、单元格上的width。很多人只在td上写width结果发现浏览器不听话原因是表格布局算法默认是auto它会根据内容重新分配宽度你写的值只是个建议。!-- colgroup 在 thead 之前按列批量设定宽度和样式 -- colgroup col classcol-region stylewidth: 18%; col span2 classcol-quarter !-- span2 一次覆盖两列 -- col classcol-total stylewidth: 20%; /colgroup这段代码里span的作用是让一个col同时作用于连续的多列避免重复写三遍。width用百分比而不是像素是为了让表格在容器变窄时按比例收缩如果某一列是金额或编号需要固定不换行就改用min-width配合white-space: nowrap。要注意colgroup必须紧跟在caption之后、thead之前位置放错时部分浏览器会直接忽略整组列定义。三个入口的关系可以这样记col负责整列td负责单格table-layout: fixed负责让浏览器别再自作主张。最后一项放到下一章展开。3. 边框、斑马纹与 table-layout样式层的三个开关结构搭好之后视觉部分基本围绕三个开关转边框合并、隔行变色、布局算法。这三个开关各自有坑而且互相影响。比如开了边框合并再设置单元格间距间距会失效开了固定布局再想让某列按内容自适应会发现内容被截断。3.1 双线边框从哪来border-collapse 的三个取值默认状态下给table和td都加边框你会看到两条线并排中间还有缝隙。原因是border-collapse的初始值是separate每个单元格各自画自己的框单元格之间靠border-spacing撑开距离。/* 合并相邻边框最常用边框不会叠加变粗 */ .data-table { border-collapse: collapse; /* 合并后 border-spacing 失效写不写都一样 */ } /* 分离边框需要圆角或卡片式单元格时用 */ .data-table--separate { border-collapse: separate; border-spacing: 0 6px; /* 水平间距 0垂直间距 6px */ } /* 只合并水平边框保留垂直分隔取值 collapse 与 separate 之外的第三种 */ .data-table--horizontal { border-collapse: collapse; /* 垂直边框单独去掉 */ } .data-table--horizontal td, .data-table--horizontal th { border-left: none; border-right: none; }三个取值里collapse用得最多适合数据密集的报表separate适合每行像一张卡片的场景配合border-radius能做圆角单元格但注意border-radius在collapse下不生效这是很多人调半天圆角出不来的原因。第三种“只合并水平边框”并不是border-collapse的取值而是用collapse加单独去掉左右边框拼出来的写法土但可控。参数上border-spacing接受一到两个长度值一个值时横竖相同两个值时第一个是水平、第二个是垂直。它只在separate下有效这点和border-collapse: collapse互斥别同时写。3.2 斑马纹与悬停nth-child 的计数边界隔行变色一般用nth-child(even)或nth-child(odd)。坑在于计数范围tbody tr:nth-child(even)只数tbody里的行从 1 开始重新计如果写成table tr:nth-child(even)表头行会被算进去斑马纹整体错位一行。/* 只在表体内隔行变色表头和表尾不受影响 */ .data-table tbody tr:nth-child(even) { background-color: #f6f8fa; } /* 悬停高亮用 :hover 覆盖斑马纹注意优先级要够 */ .data-table tbody tr:hover { background-color: #eaf3ff; } /* 键盘可访问让行可以获得焦点并显示同样的高亮 */ .data-table tbody tr:focus-within { outline: 2px solid #6aa9ff; outline-offset: -2px; }这段样式里nth-child前面必须带tbody这是边界。hover和斑马纹的优先级相同谁写在后面谁生效所以悬停规则要放在斑马纹之后。focus-within是给键盘用户的后悔药如果行内有可点击元素用 Tab 键聚焦时:hover不会触发没有这条规则键盘操作的人根本看不出当前在哪一行。另外提醒一句nth-child数的是所有同级元素不只是tr。如果tbody里混进了注释以外的其他标签比如某些模板引擎会插入占位节点计数会偏。稳妥做法是用tr:nth-of-type(even)它只数tr。3.3 table-layout: fixed 与列宽分配表格布局算法有两个值auto和fixed。auto是默认值浏览器先读完所有单元格内容再算列宽内容长的列会变宽短的自然收窄。这个行为在数据量小的时候很舒服但行数一多、某列出现一个超长字符串整张表就会突然变形加载过程中还能看到明显的重排。.data-table { width: 100%; table-layout: fixed; /* 列宽由第一行和 col 决定不再读全部内容 */ } /* 固定布局下超长内容必须自己处理溢出否则会被裁掉 */ .data-table td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* 需要换行的列单独放开 */ .data-table td.cell-wrap { white-space: normal; word-break: break-word; }fixed的规则是列宽只看col的设定和第一行单元格的宽度后面的内容一律不管。好处是渲染快、列宽稳定代价是内容溢出后默认被裁切必须自己加overflow和text-overflow。参数上width: 100%配合table-layout: fixed是最常见的组合前者让表格填满容器后者让浏览器按比例分列。如果表格不需要撑满把width改成auto此时fixed依然生效列宽按内容的第一行来定。有个细节容易忽略固定布局下如果某列没设宽度剩余空间会平均分给这些列。所以列宽要么全设要么只设关键的几列剩下的交给平均分别设一半留一半又期望特定结果。4. 合并单元格colspan 和 rowspan 的行列账合并是表格里最容易算错的部分因为它不是视觉操作而是坐标运算。写下一个rowspan3之后下面两行的对应位置就不能再放单元格了否则整行右移。这一章把坐标模型摊开再给一个能对照着改的实例。4.1 坐标模型合并到底占了几格把表格想成一个二维数组行是 y 轴列是 x 轴。colspann表示这个单元格在 x 方向占 n 格rowspann表示在 y 方向占 n 格。合并之后被占用的坐标不再接受新单元格浏览器遇到多余的td会把它挤到下一行于是出现“明明写了 4 列却渲染成 3 列”的错位。判断某一行该写几个td方法是先列出这一行理论上应有的列数再减去被上方rowspan占掉的格子数剩下的才是你要写的单元格数。比如一张 4 列表第二行第一格被上一行的rowspan2占了那这一行只需写 3 个td。这个减法每次合并都要重算没有捷径。有一个检查习惯值得养成把每行的colspan加起来加上被上方占用的格子数结果应该等于总列数。不等就说明有一行写多了或写少了浏览器不会报错只会默默错位。4.2 一个带合并的排班表实例下面这张表模拟一周排班第一列日期用rowspan合并上下午表头“时段”跨两列。这类结构在课程表、值班表里很常见。table classschedule caption某项目组一周值班安排/caption thead tr !-- 左上角空单元格同时占两行两列避免表头错位 -- th rowspan2 scopecol日期/th th colspan2 scopecolgroup时段/th /tr tr !-- 第二行只写两个因为第一列已被上方 rowspan 占用 -- th scopecol上午/th th scopecol下午/th /tr /thead tbody tr !-- 日期占两行所以下一行不再写日期单元格 -- th rowspan2 scoperowgroup周一/th tdA 同学/td tdB 同学/td /tr tr !-- 这一行只写两个 td第一格被上方的 rowspan 占掉 -- tdC 同学/td tdA 同学/td /tr tr th scoperow周二/th tdB 同学/td tdC 同学/td /tr /tbody /table逻辑说明表头第一行的“日期”用rowspan2占了两行所以第二行不能再写日期表头否则会多出一列。“时段”用colspan2横跨上午和下午两列第二行再分别写这两个子标题。表体里“周一”用rowspan2合并上下午两行紧随其后的那一行就只写两个td。如果把这一行的td写成三个第三个会被挤到下一行开头整张表从那里开始全部右移一格。参数上scopecolgroup用于横跨多列的表头scoperowgroup用于纵跨多行的行标题这两个取值比col/row更准确地描述了合并关系读屏软件会按组合来朗读。合并数超过 3 时建议在源码里加一行注释标明“本行少写一格是因为上方 rowspan”接手的人少踩一次坑。4.3 合并之后的样式补救合并单元格会带来两个视觉问题边框在某些浏览器里显示不全以及合并后的单元格内容默认顶对齐看起来和旁边不协调。.schedule th, .schedule td { border: 1px solid #d0d7de; padding: 8px 12px; vertical-align: middle; /* 合并后内容居中避免顶对齐的突兀感 */ } /* 合并单元格的边框在 collapse 下偶发缺失用背景色兜底 */ .schedule th[rowspan], .schedule th[colspan] { background-color: #f6f8fa; } /* 表头粘性定位长表格滚动时表头不跑掉 */ .schedule thead th { position: sticky; top: 0; z-index: 1; background-color: #fff; }vertical-align: middle是合并后最该加的一条默认的baseline会让占两行的单元格内容贴着上边视觉上像没对齐。position: sticky用在thead th上滚动时表头固定在顶部注意必须给th一个不透明背景否则下方内容会透出来。z-index是为了盖住表体单元格的边框。5. 避坑排查宽度撑破、错位、移动端挤压的五个现场这一章按“现象、原因、解决”三段式记五个真实发生过的现场。表格的问题大多不是报错而是渲染结果和预期差一点排查时没有控制台提示只能靠对照结构找。5.1 表格宽度撑破父容器现象容器明明设了宽度表格却横向溢出页面出现横向滚动条。原因table-layout为auto时浏览器按内容算列宽某个单元格里有长串不换行的文本或一张固定宽度的图片整张表就被撑开width: 100%在这种情况下只是下限不是上限。解决先给表格加table-layout: fixed和width: 100%再给需要换行的单元格加word-break: break-word给不需要换行的加overflow: hidden配合text-overflow: ellipsis。如果溢出的是图片给img加max-width: 100%。5.2 合并单元格后整行右移现象某一行开始后续所有列的内容都往右偏了一格表头和数据对不上。原因该行少写或多写了一个td。上方有rowspan占用时这一行的单元格数应该减少漏减就会多出一个被浏览器挤到行尾。解决回到合并开始的那一行数清楚每个colspan和rowspan逐行做加法核对总列数。临时排查可以在浏览器开发者工具里选中该行看计算后的单元格数量是否等于表头列数。5.3 移动端表格被压成一条线现象手机上每个单元格只剩一两个字宽内容竖着排完全不可读。原因表格默认会尽量塞进容器列一多就均分宽度每列都窄到放不下内容。解决给表格外层套一个横向滚动容器让表格保持最小宽度超出部分滚动查看。!-- 外层容器负责滚动表格本身保持可读的最小宽度 -- div classtable-scroll table classdata-table.../table /div.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; /* 移动端惯性滚动 */ } .table-scroll .data-table { min-width: 640px; /* 低于这个宽度就横向滚动不再压缩列 */ }min-width的值按列数和内容定4 列以内一般 480px 够用6 列以上给到 720px 左右。注意滚动容器不要同时设overflow: hidden否则滚动条被吃掉。5.4 复制到表格软件后列错位现象从页面选中表格复制粘贴到表格软件里合并单元格的位置全乱或者数字变成了文本。原因合并单元格在纯文本剪贴板里没有对应表示粘贴方只能按制表符切分另外单元格里如果有换行或制表符会被当成列分隔符。解决需要导出的场景别依赖复制直接用脚本把数据转成带分隔符的文本或者生成对应的文件格式。如果只是偶尔复制先去掉单元格内的制表符和换行再看粘贴结果。5.5 打印时表头不重复、行被截断现象打印多页表格第二页开始没有表头或者某一行被从中间切开分印在两页上。原因表头没有放在thead里浏览器不知道它是表头行被截断是因为没有禁止行内分页。解决确认表头在thead内然后加打印样式media print { /* 表头在每页顶部重复 */ thead { display: table-header-group; } /* 禁止单行跨页断开 */ tr { break-inside: avoid; } /* 打印时去掉多余背景省墨且更清晰 */ .data-table tbody tr:nth-child(even) { background: none; } }display: table-header-group是让thead重复的关键部分浏览器默认就支持显式写上更稳。break-inside: avoid作用于tr避免一行被切开。6. 从静态到可维护数组渲染与行列自检压缩包里的示例是静态 HTML行数固定改数据要手动改标签。实际项目里表格数据来自接口或配置手写标签既慢又容易错位。这一步把静态表格改成由数组驱动并加一道行列数自检把合并错位挡在渲染阶段。6.1 用数组和模板字符串渲染表格先定义数据结构行和列都带合并信息渲染时按声明生成标签。// 每一行声明 cellscolspan/rowspan 缺省为 1 const rows [ { cells: [{ text: 周一, rowspan: 2 }, { text: A 同学 }, { text: B 同学 }] }, { cells: [{ text: C 同学 }, { text: A 同学 }] }, // 第一格被上方 rowspan 占用 { cells: [{ text: 周二 }, { text: B 同学 }, { text: C 同学 }] }, ]; // 渲染函数把声明转成 tr/td并给合并单元格补上属性 function renderRows(rows) { return rows .map((row) { const tds row.cells .map((cell) { const colspan cell.colspan ? colspan${cell.colspan} : ; const rowspan cell.rowspan ? rowspan${cell.rowspan} : ; return td${colspan}${rowspan}${cell.text}/td; }) .join(); return tr${tds}/tr; }) .join(); } document.querySelector(.schedule tbody).innerHTML renderRows(rows);这段代码的关键是数据里不写“空占位”单元格被rowspan占掉的位置直接不出现在数组里由渲染函数原样输出。这样数据和视觉一一对应改排班只改数组不用再数该少写几个td。参数上colspan和rowspan缺省不输出属性浏览器按 1 处理避免生成一堆colspan1的冗余标签。要注意拼接字符串时对文本做转义用户输入里如果有尖括号会破坏结构实际项目里换成textContent赋值或引入转义函数。6.2 渲染后自检行列数渲染完加一段校验把错位挡在开发阶段而不是等用户发现。// 校验每一行的实际列数含被上方 rowspan 占用的格子应等于表头列数 function checkColumns(table) { const headerCount table.querySelectorAll(thead tr:last-child th).length; const occupied new Array(headerCount).fill(0); // 记录每列被占用的行数 const problems []; table.querySelectorAll(tbody tr).forEach((tr, rowIndex) { let col 0; tr.querySelectorAll(th, td).forEach((cell) { // 跳过被上方 rowspan 占用的列 while (occupied[col] 0) { occupied[col] - 1; col 1; } const colspan cell.colSpan || 1; const rowspan cell.rowSpan || 1; for (let i 0; i colspan; i) { occupied[col i] rowspan - 1; } col colspan; }); if (col ! headerCount) { problems.push(第 ${rowIndex 1} 行占 ${col} 列表头为 ${headerCount} 列); } }); return problems; }逻辑是逐行扫描单元格遇到被上方rowspan占用的列就跳过并递减计数最后累计本行占用的列数与表头列数比对。返回空数组说明结构正常返回内容直接打印到控制台。这段校验放在开发环境执行即可生产环境可以关掉避免每次渲染都多跑一遍遍历。6.3 上线前的三条检查第一条把表格放进最窄的容器里看一眼确认没有横向溢出需要滚动的场景滚动条能正常出现。第二条用键盘 Tab 键走一遍确认悬停高亮的行在聚焦时也有可见反馈合并单元格的scope没有写错。第三条打印预览一页以上确认表头在第二页重复、没有行被拦腰截断。我自己在这类源码上吃过一次亏合并单元格的表头看着对导出的数据却整体错一列查了半天才发现是表头第二行多写了一个th浏览器把它挤到行尾肉眼完全看不出来。从那以后我每次改完表格结构都会先跑一遍 6.2 那段校验再去看视觉。希望帮到你。本文还有配套的精品资源点击获取