
简介这是一份面向 HTML 初学者与网页制作入门者的简单表格练习源码包围绕表格结构搭建、行列内容排版与基础页面呈现展开也可用于课堂示例、课后作业对照或自学练手适合刚接触网页结构、需要理解表格语义与常见写法的学习者。压缩包共 5 个文件约 30KB包含 1 个 HTML 页面源码、1 个 Markdown 说明文档和 3 张 JPG 图片素材分别用于承载页面结构、补充使用说明与提供配图展示体量轻巧便于直接打开阅读和修改。已有 203 人学习下载。读者可从中查看 table、tr、td 等基础标签的组织方式理解表头、行列内容与图文素材在页面中的配合方法并结合说明文档快速把握源码用途对需要完成简单表格作业、准备教学演示或积累基础页面结构案例的人来说具有较好的参考与复用价值。1. 一张后台表格的返工这份源码包先解决什么问题上周帮朋友改一个后台页面一张订单明细表让我返工三次第一版rowspan算错一行右侧整块数据错位第二版加上position: sticky之后表头边框集体消失第三版拷进现有工程全局样式把表格边框全吃了。手写表格不是不会是每次都要重新踩一遍这些坑。这份 HTML 制作简单的表格源码包价值就在于把这些重复劳动固化成了一份可复用的 HTML 表格源码解压后直接打开入口页就能看到几类成品表格改单元格内容就能上线。它适合两类人刚学 HTML、想弄明白thead/tbody/tfoot到底怎么分工的新手以及临时要交一版静态报表页、不想为一张表引入整套组件库的前端。下面按拆包、跑起来、拆结构、改样式、二次开发的顺序把里面的东西过一遍。2. 解压后的目录一套不用构建工具就能跑的静态页拿到压缩包第一件事不是打开页面看效果而是先看清文件职责。这类纯 HTML 的表格示例包通常没有package.json也没有node_modules双击index.html就能出效果这意味着它的调试成本极低同时也意味着它没有任何模块化能力复用只能靠复制粘贴。先把目录摸清楚后面改哪一行、动哪个文件心里才有数。2.1 目录结构与每个文件的分工常见做法是按“一个示例一个页面”来拆分公共样式抽到css/下交互脚本放js/下。整理之后大致是这么一棵树你手上的包文件名可能略有出入但层次基本一致table-demo/ ├── index.html # 入口页罗列并链接所有表格示例 ├── basic.html # 基础表格caption thead/tbody/tfoot 全标签 ├── merge.html # 合并单元格colspan / rowspan 的实际坐标 ├── striped.html # 斑马纹 鼠标悬停高亮 ├── sticky-header.html # 固定表头 内部滚动容器 ├── responsive.html # 窄屏横向滚动 / 卡片化重排 ├── css/ │ ├── reset.css # 极简重置只清 margin、box-sizing、border │ └── table.css # 表格主样式所有示例共用 └── js/ └── sort.js # 可选点击表头做前端排序文件职责对应的改动成本差别很大先看这张表能省掉不少“改了半天没反应”的时间文件主要职责改动频率注意点index.html示例导航串起各个 demo 页低上线前一般整个删掉或改成真实入口basic.html语义化骨架示范中改结构优先改这里别直接动样式merge.html合并单元格示范高加减行必须重算rowspancss/table.css边框、斑马纹、固定表头高全局选择器多移植时要加作用域js/sort.js表头点击排序低只处理纯文本单元格含表单的列会失效能看到basic.html和merge.html是拆开的两个文件这个拆分方式挺讲究合并单元格的页面一旦和基础示例揉在一起你改样式的时候根本分不清哪条规则是给colspan服务的。我一般保留这种“一坑一页”的结构等改造完自己的版本再合并。2.2 为什么坚持纯 HTML CSS纯静态页面的好处是可以直接双击打开不需要起本地服务也不需要等编译。调试一张表格最痛苦的不是写而是改完看不到效果还要等热更新静态页在这件事上几乎是零延迟改完存盘刷新就完事。第二个好处是它把“结构问题”和“框架问题”彻底隔离了当表格错位时你可以百分百确定是 HTML 写错了而不是某个组件库的插槽把tr包了一层div。它的边界也很明确。一旦出现下面三种情况就该考虑换成组件化的方案了数据量大到需要虚拟滚动几千行以上纯 DOM 会直接卡住需要列拖拽、列显隐、复杂筛选这类重交互多个页面要复用同一套表格纯静态只能靠复制改一处要改十处。除此之外的静态报表、导出用页面、说明文档里的参数表用这套东西完全够用而且体积小到可以忽略。2.3 入口页的加载顺序打开index.html先看head里的引用顺序这个顺序不是随便排的重置样式必须在前主样式在后否则你写的边框会被重置规则覆盖掉。典型的入口页长这样!DOCTYPE html html langzh-CN head meta charsetUTF-8 !-- 窄屏必须带这一行否则媒体查询按 980px 的虚拟视口算怎么调都不生效 -- meta nameviewport contentwidthdevice-width, initial-scale1.0 title表格示例集合/title !-- 顺序不能反reset 先清默认样式table.css 再覆盖 -- link relstylesheet hrefcss/reset.css link relstylesheet hrefcss/table.css /head body main classpage h1表格示例/h1 ul classdemo-list lia hrefbasic.html基础表格thead / tbody / tfoot/a/li lia hrefmerge.html合并单元格colspan 与 rowspan/a/li lia hrefsticky-header.html固定表头/a/li lia hrefresponsive.html窄屏适配/a/li /ul /main !-- 脚本放 body 末尾避免阻塞首屏渲染 -- script srcjs/sort.js/script /body /html这里有两处容易被忽略。viewport那行如果漏了media (max-width: 768px)在手机上永远命中不了你会以为是媒体查询写错了其实是虚拟视口宽度的问题。脚本放/body之前而不是head里是因为sort.js要在 DOM 解析完之后去抓thead th放前面就得套一层DOMContentLoaded多一层嵌套多一个出错点。reset.css里通常只有短短几行但就是这几行决定了后面所有宽度计算是不是可控/* 极简重置只做三件事别加太多加多了反而难排查 */ *, *::before, *::after { box-sizing: border-box; /* 关键padding 和 border 算进宽度表格列宽才好算 */ } body { margin: 0; font-family: system-ui, -apple-system, Segoe UI, sans-serif; font-size: 14px; color: #1f2329; } table { border-collapse: collapse; /* 让相邻单元格共享一条边框而不是各画各的 */ }box-sizing: border-box是整个样式层的地基。默认的content-box下你给单元格设width: 120px再加padding: 0 12px实际占位是 144px一列列累加下来横向滚动条莫名其妙就出来了。border-collapse: collapse则是让表格看起来像一张“网格”而不是“一堆带边小方块”后面的斑马纹、固定表头边框都依赖它。3. 表格骨架thead/tbody 与 colspan/rowspan 的坐标账结构层是这套源码最值得看的部分。很多人写表格只写table tr td浏览器照样渲染但一旦涉及固定表头、打印分页、屏幕阅读器朗读缺的那几个标签就会集中爆雷。先把骨架立对样式怎么改都不会跑偏。3.1 六个标签搭出最小可用表格一张合格的数据表至少要有caption、thead、tbody、tfoot、th、td六种元素参与。下面这段是包里basic.html的核心结构可以直接当模板抄table classdata-table !-- caption 是表格标题不是装饰屏幕阅读器会先读它 -- caption2024 年第一季度各小组工时统计示例数据/caption thead tr !-- scopecol 明确告诉辅助技术这一格管的是它下面那一列 -- th scopecol小组/th th scopecol人数/th th scopecol总工时/th th scopecol人均工时/th /tr /thead tbody tr !-- scoperow 相反管的是它右边那一行 -- th scoperow甲组/th td8/td td1,240/td td155.0/td /tr tr th scoperow乙组/th td6/td td980/td td163.3/td /tr /tbody tfoot tr th scoperow合计/th td14/td td2,220/td td158.6/td /tr /tfoot /tablethead和tfoot不只是“分个组好看”。打印的时候浏览器会把thead的内容重复到每一页顶部tfoot重复到底部这是div拼出来的假表格永远做不到的。tbody同样有意义一张表里可以有多个tbody做数据分段比如“已确认 / 待确认”两块中间还能插一行分段标题。th和td的区别也别乱用。第一列如果是行标题比如上面的“甲组”就该用th scoperow这样屏幕阅读器读某一格时会先说“甲组人数8”而不是干巴巴地念“8”。这类细节在纯视觉验收里完全看不出来但改起来只要换个标签成本极低。3.2 合并单元格先画网格再填内容colspan和rowspan是这张表里最容易翻车的地方血泪经验是不要一边写 HTML 一边算跨行先在纸上或表格软件里画一个 N×M 的网格标好每个格子归谁再落成代码。下面是一个 5 列 3 行的排班表第一列跨两行表头第一行跨两列table classdata-table schedule caption某小组排班示例数据/caption thead tr !-- 这一格同时跨 2 行、占 1 列 -- th scopecol rowspan2小组/th !-- 这一格占 2 列、跨 1 行 -- th scopecolgroup colspan2上午/th th scopecolgroup colspan2下午/th /tr tr !-- 第一行已经被 rowspan 占掉一格所以这一行从第 2 列开始写共 4 格 -- th scopecol在岗/th th scopecol轮休/th th scopecol在岗/th th scopecol轮休/th /tr /thead tbody tr !-- rowspan2 表示这一格向下吃两行 -- th scoperow rowspan2甲组/th tdA 同学/td tdB 同学/td tdC 同学/td tdD 同学/td /tr tr !-- 上一行的 rowspan 已经占住了本行第 1 列这里只写后面 4 格写 5 格就会多出一列 -- tdE 同学/td tdF 同学/td tdG 同学/td tdH 同学/td /tr /tbody /table校验规则只有一条记住就能自己查错每一行实际占用的列数 该行自己写的单元格数目 上方rowspan伸下来占住的格子数结果必须等于表格总列数。上面第二行只写了 4 个td但“甲组”那个rowspan2从上一行伸下来占了一格4 1 5与总列数一致。如果哪天你发现表格右边莫名其妙缺一块或者多出一条空白列99% 就是某一行这个等式不成立。rowspan写在th上还有个副作用那一格的内容会垂直居中视觉上像是“管着”下面几行这正好符合语义。但如果你写的是td记得检查对齐默认样式下td是左对齐跨行时看起来会很怪。3.3 语义化与无障碍caption、scope 和 aria前面已经用到了caption和scope这里补两个包里没写但你改的时候一定会用到的点。第一个是复杂表头的关联当表格有两级表头、scope不足以表达归属时可以给th加id再在td上用headers指过去table classdata-table thead tr th idcol-name scopecol姓名/th th idcol-q1 scopecol一季度/th th idcol-q2 scopecol二季度/th /tr /thead tbody tr td headerscol-nameA 同学/td !-- 一个单元格对应多个表头时id 用空格隔开 -- td headerscol-q1 col-name320/td td headerscol-q2 col-name410/td /tr /tbody /table第二个是空数据状态。数据为空时不要留一张光秃秃的表头塞一行占满整行的提示colspan填表格总列数tr classempty-row !-- colspan 必须等于当前表格总列数否则这行会缩成半截 -- td colspan4暂无数据请调整筛选条件后重试/td /tr这一行的colspan是个隐藏地雷你后来给表格加了一列忘了改这里的数字空状态那行就会短一截看着就像表格崩了。3.4 操作列与内部元素的对齐很多表格最后一列是“编辑 / 删除”这类操作按钮。这类列有一个共同问题按钮高度把行撑高了和纯文本行高度不一致看着参差不齐。包里的做法是给这一列的格子加一个统一的cell-actions类用 flex 对齐td classcell-actions button typebutton classbtn btn-link>.cell-actions { display: flex; gap: 8px; align-items: center; /* 按钮垂直居中对齐行高不会被撑乱 */ white-space: nowrap; /* 按钮文字别换行否则一行变两行 */ } .cell-actions .btn { padding: 2px 8px; font-size: 12px; line-height: 20px; /* 行高定死一行内所有按钮高度一致 */ }white-space: nowrap这条看着不起眼但窄屏下如果没有它“删除”两个字会被拆成两行整行高度暴涨前面的斑马纹就全乱了。4. 样式层拆解斑马纹、固定表头、窄屏适配三件套结构写对之后剩下 80% 的工作量都在样式上。这一层的关键是理解几个 CSS 属性之间的耦合关系border-collapse影响边框怎么画position: sticky依赖滚动容器min-width决定横向滚动条出不出现。改一处忘一处就会出现“表头固定住了但边框没了”这种典型翻车。4.1 border-collapse 与斑马纹的正确打开方式斑马纹的核心是:nth-child但它有个前提容易被忽略选择器要挂在tbody tr上而不是直接挂tr。因为一旦挂在整个tr上thead的那一行也会参与计数某些浏览器下会出现表头也被染色的问题。/* 斑马纹只在 tbody 内生效表头不参与 */ .data-table tbody tr:nth-child(even) { background-color: #f7f8fa; } /* 悬停高亮优先级要高于斑马纹所以写在后面 */ .data-table tbody tr:hover { background-color: #eef4ff; } /* 边框统一走 collapse 模式只给 td/th 画右下边框避免双线 */ .data-table th, .data-table td { padding: 10px 12px; border-bottom: 1px solid #e5e6eb; text-align: left; } /* 数字列右对齐更易读用类名控制别用 :last-child 猜 */ .data-table .num { text-align: right; font-variant-numeric: tabular-nums; /* 等宽数字列对齐不会跳 */ }font-variant-numeric: tabular-nums是数字列的一个小技巧。默认字体里“1”比“8”窄右对齐之后小数点对不齐看起来一行行歪歪扭扭加上这一条所有数字占同样宽度纵向扫读舒服很多。另外注意:hover必须写在:nth-child后面两者优先级相同靠顺序决胜负写反了悬停就没有效果——这类问题排查起来最耗时因为规则明明写了。还有一个坑display: none隐藏掉的行仍然参与:nth-child计数。你要是用 JS 做前端筛选、直接给行加display: none斑马纹就会变成“隔一行白、隔一行也白”的错乱状态。解决办法是筛选时改用hidden属性配合tbody tr:not([hidden])重新计数或者干脆筛选后重建tbody。4.2 固定表头sticky 的父容器陷阱position: sticky想要生效元素必须有一个可滚动的祖先容器而且这个容器不能是overflow: hidden。包里sticky-header.html的做法是套一层滚动容器/* 滚动容器高度定死超出部分由它来滚而不是整个页面滚 */ .table-scroll { max-height: 420px; overflow: auto; border: 1px solid #e5e6eb; border-radius: 6px; } .table-scroll thead th { position: sticky; top: 0; /* 贴住滚动容器顶部 */ z-index: 2; /* 要高于单元格内容否则数据会从表头下面透出来 */ background-color: #fafbfc; /* 必须不透明透明的话下面的字会透上来 */ /* border-collapse: collapse 之后 sticky 表头的下边框会消失用内阴影补一条 */ box-shadow: inset 0 -1px 0 #e5e6eb; }这段里有两个必须解释的点。z-index: 2不加的话滚动时下面的数据行会盖住表头看着像表头“破了”background-color必须是不透明色很多人图省事写background: transparent或者干脆不写滚动起来就是糊成一片。最关键的是那条box-shadow。在border-collapse: collapse模式下thead的下边框其实是属于“两个单元格共享的那条线”一旦表头脱离文档流固定住共享边框的绘制就被破坏了于是表头下边框凭空消失。换成border-collapse: separate能保留边框但会在单元格之间多出缝隙得靠border-spacing: 0补回来。我试过几次之后还是觉得内阴影更省事改一个属性就解决不动全局的 collapse 设置。4.3 窄屏横向滚动容器优先窄屏下的表格有两种处理思路横向滚动或者卡片化重排。前者改动小、信息不丢后者阅读体验好但要把每个td的列名重复显示一遍。这份源码主要以横向滚动为主外面套一层容器给表格一个最小宽度/* 窄屏容器只让表格横向滚页面本身不滚 */ .table-responsive { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; /* 移动端惯性滚动 */ } .table-responsive .data-table { /* 小于这个宽度就出现滚动条避免每列被压成一条线 */ min-width: 720px; width: 100%; }min-width是关键参数一般按“列数 × 每列最小可读宽度”来估6 列以内给 640px8 列左右给 720px10 列以上给 900px 并考虑固定前两列。overflow-x: auto放在包裹容器上而不是body上是为了保证页面本身不会整体横向滚动——整页横滚是最难受的体验用户一滑就找不到北。卡片化重排的做法是用媒体查询把thead藏掉再用::before把列名注进每个格子media (max-width: 640px) { .table-card thead { /* 视觉上隐藏但仍保留给屏幕阅读器 */ position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); } .table-card tbody tr { display: block; border: 1px solid #e5e6eb; border-radius: 6px; margin-bottom: 12px; } .table-card tbody td { display: flex; justify-content: space-between; border-bottom: none; } /* 用>media print { /* 保证表头在每一页顶部重复 */ thead { display: table-header-group; } tfoot { display: table-footer-group; } /* 一行尽量别跨页断开 */ tr { break-inside: avoid; page-break-inside: avoid; /* 老浏览器兼容写法 */ } /* 打印时去掉悬停和斑马纹省墨也更清楚 */ .data-table tbody tr:hover { background: none; } .table-scroll { max-height: none; /* 打印不要滚动条一次性铺开 */ overflow: visible; } }display: table-header-group是这段的核心。thead默认就是这个值但有些重置样式会把它改成block一改打印分页就失效了所以打印样式里显式写一遍更稳。break-inside: avoid配合max-height: none能避免那张固定表头的表在打印时只输出可视区内的几行——这个坑我踩过一次导出的 PDF 只有七八行数据查了半天才发现是滚动容器的max-height在打印时仍然生效。5. 排错手册表格渲染的五个常见翻车现场下面这五条基本覆盖了改这套源码时能遇到的大部分问题。每一条都按“现象 → 原因 → 解决”走遇到对得上的直接照着改。5.1 结构类合并单元格错行与多余空白现象表格右侧凭空多出一列空白或者某一行的数据整体右移一格删掉重写一遍有时又好了。原因某一行实际占用的列数和总列数对不上。最常见的是上一行写了rowspan2下一行还老老实实把第一格也写了等于多占了一列另一种是给表格加了一列但某个跨列的表头colspan数字没跟着加。解决把每一行单独数一遍“自己写的格子数 上方伸下来的格子数”跟表头总列数对齐。colspan的累加也要顺着数一个colspan3的格子在数学上等于三格但它只写了一个标签很多人只数标签不数跨度越数越乱。改完用浏览器开发者工具选中表格看每一行的高亮宽度是否一致能立刻定位到出问题的那一行。5.2 样式类边框消失、表头错位、列宽不生效现象一加了position: sticky之后表头下边框没了滚动时像浮在半空。原因border-collapse: collapse下边框由相邻单元格共享绘制表头脱离文档流固定后这条共享线不再跟着走。解决用box-shadow: inset 0 -1px 0 #e5e6eb补一条内阴影别去改border-collapse的全局设置。现象二给某列设了width: 120px实际渲染出来宽了一大截。原因表格默认table-layout: auto列宽由内容撑开你设的宽度只是“建议值”。解决给表格加table-layout: fixed并显式给出总宽width: 100%此时列宽才会严格按设置分配代价是超长内容不会撑开列需要配合overflow: hidden; text-overflow: ellipsis; white-space: nowrap做截断。现象三斑马纹错位隔行颜色对不上。原因用display: none隐藏了行但:nth-child仍然把它算进计数。解决改成hidden属性 tbody tr:not([hidden]):nth-child(even)或者筛选后重建tbody再重新套色。5.3 移植类拷进现有工程后样式全乱现象单个页面打开完美把 HTML 和 CSS 拷进现有项目之后边框变粗、字号变大、行高塌了甚至按钮变成浏览器默认样式。原因table.css里大量使用.data-table th、.data-table td这类相对通用的选择器而现有工程往往有自己的全局重置规则两边打架之后谁赢取决于加载顺序和优先级结果就是玄学。解决给整张表套一层唯一的作用域类把样式文件里所有选择器都加上这个前缀同时检查现有工程的box-sizing设置如果全局是content-box表格列宽会全部算错。移植时我一般会先在目标工程里单独建一个空白页跑通确认没问题再往业务页里嵌避免两个变量同时变动导致定位不到原因。5.4 数据类内容全是数字却对不齐现象数字列右对齐之后小数点上下错位看起来像抖动。原因默认字体里阿拉伯数字宽度不等宽“1”和“8”占位不同。解决给数字列加font-variant-numeric: tabular-nums。如果内容里混了千分位逗号和单位建议在 HTML 里用td classnum>// 给所有带 style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />