1. 项目概述:为什么一个“表结构”练习能卡住80%的初学者?
HTML 表结构,表面看就是<table><tr><td>这几个标签来回嵌套,但实际教学和项目带练中,我见过太多人在这里栽跟头——不是写不出来,而是写出来的东西一上线就崩:表格错位、文字挤成一团、响应式下直接溢出屏幕、用 CSS 想调个边框却连哪一层该加border-collapse都搞不清。更麻烦的是,这三个小练习看似独立,实则层层递进,暗藏了 HTML 结构语义化、CSS 布局控制力、数据呈现逻辑这三重能力门槛。你可能刚学完标签语法,一做第一个练习就发现:明明按教程写了<th>,表头却没加粗;第二个练习加合并单元格时,rowspan="2"写对了,但整行布局全乱;第三个练习要让表格在手机上可横向滑动,结果加了overflow-x: auto,父容器却没设width,反而把滚动条压没了。这不是手生,是没吃透“表格本质是二维数据容器,不是排版画布”这个底层逻辑。这三个小练习,本质上是在训练你用 HTML 描述“谁属于哪一行、哪一列、跨几格”,再用 CSS 控制“它看起来多宽、多高、怎么对齐、怎么呼吸”。适合刚学完基础标签、正准备接触 Bootstrap 或 Vue 表格组件的前端新人;也适合转行做数据分析展示、需要快速搭内部报表页面的业务人员;甚至适合教孩子编程的家长——因为它的错误反馈极其直观:写错了,表格立刻变形,不用调试器就能看见问题。下面我们就从设计思路开始,一层层剥开这三层练习背后的硬核细节。
2. 整体设计与思路拆解:三个练习不是堆砌,而是构建“结构-样式-交互”认知闭环
2.1 为什么必须是“三个”?而不是一个大表格或五个小片段?
我带过几十期前端入门班,反复验证过:单一大表格容易让初学者陷入“抄代码”陷阱,只记住了标签顺序,没理解每一层嵌套的职责;而碎片化练习(比如只练<th>或只练colspan)又缺乏上下文,学完不知道用在哪。这三个练习,是我根据真实项目高频场景提炼出的最小可行闭环:
练习一:标准课程表→ 解决“结构正确性”问题
它强制你区分<thead>和<tbody>,理解<th>不仅是“加粗文字”,更是语义上的“列标题”,浏览器读屏器会据此朗读;它要求你用<tr>明确划分“行逻辑”,避免把所有<td>堆在<tbody>里造成结构松散。这个练习不碰 CSS,纯靠 HTML 标签嵌套层级来建立“二维坐标系”直觉。练习二:学生成绩汇总表→ 解决“结构复杂度”问题
引入rowspan和colspan合并单元格,这是绝大多数人第一次面对“一个单元格占据多个逻辑位置”的挑战。关键不在语法(rowspan="3"很好写),而在计算:当你给第一行第一列加rowspan="3",意味着后面两行的第一列<td>必须跳过,否则表格引擎会认为你多画了一列,导致整行错位。这个练习逼你动手画草图、数格子,把抽象的“跨行”转化为可视的网格坐标。练习三:响应式商品清单表→ 解决“结构与样式协同”问题
前两个练习只关注“能不能显示”,这个练习直击“好不好用”。它要求你主动为表格包裹<div class="table-wrapper">,再用 CSS 控制.table-wrapper { overflow-x: auto; },而不是指望表格自己适应屏幕。这里埋了一个关键认知:HTML 表格本身不具备响应式能力,它只是数据容器,响应式是 CSS 层的职责。很多初学者卡在这一步,是因为他们试图用width="100%"或max-width直接作用于<table>,却忽略了父容器的宽度约束和white-space: nowrap对内容换行的干预。
这三个练习合起来,不是教你“怎么写表格”,而是训练你建立一套完整的思维链:先用 HTML 精确描述数据关系(谁是谁的标题、谁跨几格),再用 CSS 精确控制视觉表现(多宽、多高、是否滚动),最后用开发者工具反向验证(检查元素看 DOM 结构是否干净、盒模型是否合理)。这种思维一旦形成,后续学任何框架的表格组件(比如 Element Plus 的el-table或 Ant Design 的Table),你都能一眼看出它的 HTML 底层结构是否健壮。
2.2 为什么放弃“传统教学路径”?不讲<caption>、<colgroup>这些“高级”标签?
坦白说,<caption>很优雅,<colgroup>对批量控制列宽极高效,但它们在入门阶段是干扰项。我试过在第一课就讲<colgroup>,结果学员花 20 分钟纠结span属性和<col>标签顺序,反而忘了<tr>必须在<tbody>里。真实项目中,95% 的简单表格根本用不到<colgroup>——你直接给<td>加width或用 CSS Grid 替代更直观。而<caption>虽然语义重要,但初学者更急需掌握的是“如何让表头<th>和数据<td>在视觉上区分开”,这完全可以通过 CSS 的font-weight和background-color实现,比记住<caption>的位置和作用更落地。我的原则是:所有被舍弃的内容,必须有更简单、更直接、更少出错的替代方案,并且这个方案在真实小项目中 100% 可用。比如控制列宽,我教的是“给第一行<th>设min-width,再用table-layout: fixed锁定”,而不是引入<colgroup>增加心智负担。等学员能稳定写出无错表格后,再回过头讲<colgroup>的优势,他们会立刻明白:“哦,原来批量控制列宽还能这么省事”。
2.3 工具链极简主义:为什么只用原生 HTML+CSS,拒绝任何框架或在线生成器?
现在有很多拖拽式表格生成器,输入数据就吐出 HTML,但这是我坚决反对的。原因很现实:生成器产出的代码往往冗余——比如<td style="text-align: center;">大量内联样式,破坏 CSS 分离原则;或者<table border="1">这种过时属性,让你误以为边框是 HTML 的责任。更危险的是,它掩盖了结构错误:生成器可能自动帮你补全缺失的<tr>,让你永远发现不了“少写一行<tr>就会导致整列偏移”这个核心规律。我坚持手写,是因为每一个<tr>的敲击、每一个</td>的闭合,都在强化你的 DOM 树直觉。就像学骑车不能一直靠辅助轮,初学表格必须亲手感受“嵌套层级断裂”的即时反馈。工具上,我只要求两点:用 VS Code(开启 Emmet 插件,table>tr*3>td*4一键生成骨架),以及必开浏览器开发者工具的“元素”面板。后者不是用来调试,而是用来“观察”——点开<table>,看它底下是不是干净的<thead><tbody><tfoot>(即使不用<tfoot>,也要知道它的存在位置);点开<tr>,确认它里面只有<th>或<td>,没有意外混入的<div>。这种肌肉记忆,是任何生成器给不了的。
3. 核心细节解析与实操要点:从标签语义到像素级控制的硬核拆解
3.1<table>的四大核心区域:为什么<thead>不是可选项,而是必选项?
很多人觉得<thead>只是为了加个背景色,其实它承担着三重不可替代的职责:
语义职责:告诉浏览器和辅助技术“这部分是表头,别把它当普通数据读”。屏幕阅读器会专门朗读
<thead>中的内容,并提示“这是表头行”,这对视障用户是刚需。如果你把所有<tr>都塞进<tbody>,再用 CSS 把第一行变粗,语义上它依然是“数据行”,辅助技术无法识别。渲染职责:现代浏览器对
<thead>有特殊优化。当表格很长(比如上百行),用户滚动时,<thead>会自动“悬浮”在顶部(需配合position: sticky),而<tbody>独立滚动。这个效果不是 CSS 魔法,而是浏览器对<thead>的原生支持。如果你没写<thead>,哪怕用 JS 模拟悬浮,性能和兼容性都差一大截。结构职责:它是
<tbody>的“锚点”。<tbody>里的每一行<tr>,其列数必须严格对齐<thead>里的<th>数量。如果<thead>有 4 个<th>,<tbody>里某行只写了 3 个<td>,浏览器会自动补一个空<td>,但这个“自动补全”极易引发错位——尤其当你用了colspan时,补全逻辑会混乱。所以,<thead>是整个表格的列定义契约,它必须存在,且必须准确。
实操中,我要求学员第一步永远是:
<table> <thead> <tr> <th>姓名</th> <th>语文</th> <th>数学</th> <th>英语</th> </tr> </thead> <tbody> <!-- 这里才开始填数据 --> </tbody> </table>注意:<thead>里必须包一层<tr>,<tr>里才是<th>。不能写成<thead><th>姓名</th><th>语文</th></thead>,这是无效结构,浏览器会自动修正,但修正逻辑不可控。
3.2<th>与<td>的本质区别:不只是加粗,而是“角色声明”
初学者常问:“<th>和<td>都能放文字,为啥非要分?”答案在于:<th>是“标题单元格”,声明‘这一格定义了它所在行/列的数据含义’;<td>是“数据单元格”,声明‘这一格是具体数值’。这个声明直接影响三件事:
默认样式:浏览器内置样式表规定
<th>默认font-weight: bold且text-align: center,<td>默认font-weight: normal且text-align: left。但这只是默认,你可以用 CSS 覆盖。重点是,即使你把<th>的字体设成细体,它的语义角色仍是“标题”,辅助技术依然会按标题处理。作用域绑定:
<th>可以通过scope属性声明它管哪一片数据。比如<th scope="col">姓名</th>表示它管整列,<th scope="row">张三</th>表示它管整行。这对复杂表格(如多级表头)至关重要。练习二的“学生成绩汇总表”中,第一列的“张三”、“李四”其实是行标题,应该用<th scope="row">,而不是<td>。这样,当屏幕阅读器读到“张三”时,会明确告知用户“这是行标题,接下来的数据属于张三”。可访问性关联:
<th>可以用id和headers属性与<td>关联。比如<th id="math">数学</th>,然后<td headers="math">85</td>,屏幕阅读器读到“85”时会说“数学 85分”。这在非规则表格(如合并单元格后)中是救命功能。
实操要点:
- 所有列标题(第一行)必须用
<th>,且scope="col"(可省略,默认值)。 - 所有行标题(第一列)必须用
<th>,且显式写scope="row"。 - 绝对不要为了“看起来像标题”而给
<td>加font-weight: bold,这是语义污染。
3.3rowspan与colspan的“格子守恒定律”:为什么你总算错跨格数?
这是三个练习里最易出错的环节。根源在于,初学者常把rowspan当作“向下拉伸”,把colspan当作“向右拉伸”,而忽略了表格引擎的“格子计数”机制。真相是:表格引擎按行扫描,每遇到一个<td>或<th>,就消耗一个“列槽位”;rowspan和colspan的作用,是让这个单元格“占用多个槽位”,但被占用的槽位,在后续行中必须被跳过。
举个经典错误案例(练习二的学生成绩表):
<!-- 错误写法 --> <thead> <tr> <th rowspan="2">学生</th> <!-- 占用第1列,跨2行 --> <th colspan="3">成绩</th> <!-- 占用第2、3、4列,跨1行 --> </tr> <tr> <th>语文</th> <!-- 错!这里本应跳过第1列,但写了,导致第1列被重复占用 --> <th>数学</th> <th>英语</th> </tr> </thead>问题在哪?第一行<th rowspan="2">学生</th>占用了第1列,并声明“接下来一行的第1列归我管”。那么第二行扫描时,第1列槽位已被预占,引擎会直接跳过它,从第2列开始计数。但上面代码第二行又写了<th>语文</th>,引擎认为这是第1列的新内容,于是强行插入,导致整行向右错位一格。
正确写法必须“跳过被占用的列”:
<!-- 正确写法 --> <thead> <tr> <th rowspan="2">学生</th> <th colspan="3">成绩</th> </tr> <tr> <!-- 第1列已由上一行的rowspan覆盖,此处必须留空,不写任何<th> --> <th>语文</th> <th>数学</th> <th>英语</th> </tr> </thead>这里第二行<tr>里只有3个<th>,对应第2、3、4列,完美对齐。这就是“格子守恒”:第一行用了1+3=4个槽位,第二行也必须用4个槽位,只不过第1个槽位被rowspan预占,所以只显式写3个。
实操口诀:写完带rowspan/colspan的行,立刻在草稿纸上画网格,标出哪些格子被占用;下一行写之前,先数清楚“还剩几个空格子”,然后只填空格子。我让学生用 Excel 画 5x5 网格,手动标rowspan=2的格子,练三次就形成条件反射。
3.4 边框控制的“三层体系”:为什么border="1"是毒药,border-collapse是解药?
HTML 4 时代流行<table border="1">,但现在这是严重反模式。原因有三:
语义污染:
border是纯粹的视觉表现,不该写在 HTML 结构层。它让 HTML 文件充斥着样式信息,违背“结构、样式、行为分离”原则。控制粒度粗:
border="1"只能开/关全局边框,无法实现“外边框粗、内边框细”、“表头无边框、数据有边框”等精细需求。渲染逻辑诡异:
border="1"会触发浏览器的“分离边框模型”(separated borders),每个<td>自己画自己的边框,相邻单元格的边框会重叠,导致实际边框宽度翻倍,且无法用 CSS 精确控制。
真正的解药是 CSS 的border-collapse属性,它建立了三层控制体系:
| 控制层级 | CSS 属性 | 作用 | 典型场景 |
|---|---|---|---|
| 全局模型 | border-collapse: collapse | 让所有单元格边框合并为一条线,消除重叠 | 所有现代表格的起点,必须设 |
| 外框控制 | table { border: 2px solid #333 } | 控制整个表格的外边框 | 突出表格边界 |
| 内框控制 | th, td { border: 1px solid #ccc } | 控制每个单元格的内边框 | 区分行列 |
关键细节:
border-collapse: collapse必须作用于<table>,不能写在<td>上。- 一旦设了
collapse,<td>的border就变成“共享边框”,两个相邻<td>的border会自动合并为一条,宽度取两者最大值。所以th { border-bottom: 2px }+td { border-top: 1px },最终显示为 2px。 - 如果想让表头无下边框、数据无上边框,用
th { border-bottom: none }和td { border-top: none },而不是试图用margin或padding挤掉。
实操中,我给学员的“边框三行咒语”是:
table { border-collapse: collapse; /* 第一行:必加,奠定基础 */ border: 2px solid #2c3e50; /* 第二行:外框,醒目 */ } th, td { border: 1px solid #bdc3c7; /* 第三行:内框,细腻 */ padding: 12px 16px; /* 顺手加上内边距,呼吸感 */ }4. 实操过程与核心环节实现:从零开始搭建三个练习的完整代码与避坑指南
4.1 练习一:标准课程表——用纯 HTML 构建无错结构骨架
这个练习的目标是:不写任何 CSS,仅靠 HTML 标签嵌套,输出一个在浏览器中能正确显示、无错位、无警告的表格。这是检验你 HTML 结构直觉的“压力测试”。
步骤一:确定数据维度,画出网格草图
课程表典型结构:
- 行:周一至周五(5行)+ 时间段(如 8:00-9:00, 9:00-10:00...,假设4节)→ 共5行数据 + 1行表头 = 6行
- 列:时间段(1列)+ 周一至周五(5列)→ 共6列
草图如下(用X表示单元格):
| 周一 | 周二 | 周三 | 周四 | 周五 | --------|-----|-----|-----|-----|-----| 8:00-9:00 | X | X | X | X | X | 9:00-10:00| X | X | X | X | X | 10:00-11:00| X | X | X | X | X | 11:00-12:00| X | X | X | X | X |注意:第一列是“时间段”,是行标题,应用<th scope="row">;第一行是“星期”,是列标题,应用<th scope="col">。
步骤二:手写 HTML,严格遵循嵌套规则
<table> <!-- 表头部分:第一行是列标题 --> <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">8:00-9:00</th> <td>数学</td> <td>英语</td> <td>物理</td> <td>化学</td> <td>生物</td> </tr> <tr> <th scope="row">9:00-10:00</th> <td>语文</td> <td>数学</td> <td>英语</td> <td>物理</td> <td>化学</td> </tr> <tr> <th scope="row">10:00-11:00</th> <td>英语</td> <td>物理</td> <td>化学</td> <td>生物</td> <td>语文</td> </tr> <tr> <th scope="row">11:00-12:00</th> <td>物理</td> <td>化学</td> <td>生物</td> <td>语文</td> <td>数学</td> </tr> </tbody> </table>提示:
<thead>里第一格<th scope="col"></th>必须存在!它保证了列数对齐(6列),否则<tbody>里每行只有5个<td>,浏览器会自动补空格,但补的位置不可控,极易错位。
步骤三:浏览器验证,揪出隐藏错误
打开 Chrome,右键“检查”,在 Elements 面板中展开<table>,逐层确认:
<thead>下只有 1 个<tr>,<tr>下有 6 个<th>(含空白那个)<tbody>下有 4 个<tr>,每个<tr>下有 1 个<th>+ 5 个<td>= 6 个单元格- 点击任意
<td>,看右侧 Styles 面板是否显示display: table-cell(证明它是合法单元格)
常见错误及修复:
- 错误:
<tbody>里某行<td>少写一个 →修复:数<th>数量,确保每行<td>数 =<thead>中<th>数 - 1(因为第一列是<th scope="row">) - 错误:
<thead>里漏了空白<th>→修复:立即补上,这是列对齐的基石 - 错误:把
<th scope="row">写成<td>→修复:语义错误,影响可访问性,必须改
步骤四:添加极简 CSS,建立视觉基准
此时加 CSS,只为验证结构,不追求美观:
table { border-collapse: collapse; width: 100%; } th, td { border: 1px solid #ddd; padding: 8px; text-align: center; } th[scope="row"] { background-color: #f2f2f2; font-weight: normal; /* 行标题不加粗,避免和列标题混淆 */ }效果:一个清晰、无错位、行列分明的课程表。至此,练习一完成——你已掌握 HTML 表格的“骨骼”。
4.2 练习二:学生成绩汇总表——攻克rowspan/colspan的格子守恒
这个练习的核心是:用最少的rowspan/colspan,实现“学生”列跨所有行、“成绩”组跨三列的复合表头。难点在于,它要求你同时处理行方向和列方向的合并。
步骤一:分析表头结构,绘制跨格矩阵
目标表头:
- 第一行:
学生(跨4行) +成绩(跨1行,但管下面3列) - 第二行:
语文、数学、英语(各占1列)
这意味着:
学生占用第1列,rowspan="4"→ 它“锁定”了第1列的全部4行成绩占用第2、3、4列,colspan="3"→ 它“锁定”了这三列的第1行- 因此,第2、3、4行的第1列必须为空(被
student占用),第2、3、4行的第2、3、4列才是数据区
矩阵表示(S=学生,C=成绩,X=数据):
行\列 | 1 | 2 | 3 | 4 | ------|------|------|------|------| 1 | S | C | C | C | 2 | (S) | X | X | X | 3 | (S) | X | X | X | 4 | (S) | X | X | X |括号(S)表示该位置被第一行的S覆盖,无需写内容。
步骤二:手写 HTML,严格执行“跳过占用列”
<table> <thead> <tr> <!-- 第1列:学生,跨4行 --> <th rowspan="4">学生</th> <!-- 第2-4列:成绩,跨1行,管3列 --> <th colspan="3">成绩</th> <!-- 注意:这里不写第5列!因为colspan="3"已占满第2、3、4列 --> </tr> <!-- 第2行:只写第2、3、4列的标题,第1列跳过 --> <tr> <th>语文</th> <th>数学</th> <th>英语</th> <!-- 第1列绝对不写! --> </tr> <!-- 第3行:同理,只写数据列标题 --> <tr> <th>平时</th> <th>期中</th> <th>期末</th> </tr> </thead> <tbody> <!-- 数据行:每行第1格是学生名(th scope="row"),后9格是数据 --> <tr> <th scope="row">张三</th> <td>85</td><td>92</td><td>88</td> <td>78</td><td>85</td><td>90</td> <td>92</td><td>88</td><td>95</td> </tr> <tr> <th scope="row">李四</th> <td>90</td><td>87</td><td>93</td> <td>82</td><td>89</td><td>91</td> <td>88</td><td>94</td><td>90</td> </tr> </tbody> </table>注意:
<thead>里第二行和第三行,都只写了3个<th>,对应第2、3、4列。第1列全程由第一行的<th rowspan="4">学生</th>覆盖。
步骤三:CSS 强化语义,用颜色区分层级
table { border-collapse: collapse; width: 100%; } th, td { border: 1px solid #bbb; padding: 10px; text-align: center; } /* 第一行表头:深色背景 */ thead tr:first-child th { background-color: #2c3e50; color: white; } /* 第二、三行表头:浅灰背景,区分层级 */ thead tr:nth-child(2), thead tr:nth-child(3) th { background-color: #ecf0f1; } /* 行标题:左对齐,加粗 */ th[scope="row"] { text-align: left; font-weight: bold; background-color: #f8f9fa; }效果:表头层次分明,“学生”纵向贯穿,“成绩”横向统领,数据区清晰可辨。此时,你已掌握表格的“肌肉”——复杂结构的构建能力。
4.3 练习三:响应式商品清单表——让表格在手机上可滚动的实战方案
这个练习直击痛点:桌面端完美的表格,放到手机上文字挤成一团,用户必须左右滑动才能看清,但默认情况下,表格不会产生水平滚动条。解决方案不是“让表格变小”,而是“让表格容器产生滚动”。
步骤一:理解“滚动失效”的根本原因
为什么直接给<table>加overflow-x: auto无效?因为:
<table>默认display: table,它的宽度由内容撑开,且overflow属性对table元素无效(规范限制)- 即使你强行设
width: 100%,如果父容器(如<body>)没有明确宽度限制,100%依然等于内容宽度,不会触发滚动
正确路径是:用一个块级容器包裹<table>,对该容器设overflow-x: auto,并确保容器有明确的宽度约束(通常是width: 100%)。
步骤二:HTML 结构封装,创建滚动容器
<!-- 关键:用 div 包裹 table,div 是滚动主体 --> <div class="table-wrapper"> <table> <thead> <tr> <th>商品ID</th> <th>商品名称</th> <th>品牌</th> <th>规格</th> <th>单价(元)</th> <th>库存</th> <th>上架时间</th> <th>操作</th> </tr> </thead> <tbody> <tr> <td>PROD-001</td> <td>无线蓝牙降噪耳机</td> <td>声悦</td> <td>黑色/单耳</td> <td>299.00</td> <td>128</td> <td>2023-05-12</td> <td><button>编辑</button></td> </tr> <!-- 更多行... --> </tbody> </table> </div>步骤三:CSS 实现响应式滚动,兼顾桌面与移动
/* 滚动容器:关键!必须设 width,否则不生效 */ .table-wrapper { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; /* iOS 平滑滚动 */ margin-bottom: 1rem; } /* 表格本身:固定布局,防止内容撑破 */ table { border-collapse: collapse; width: 100%; /* 这里 width: 100% 指“占满容器”,不是“占满屏幕” */ table-layout: fixed; /* 关键!锁定列宽,避免内容过长导致列宽失衡 */ } /* 强制单元格内容不换行,让滚动有意义 */ th, td { border: 1px solid #e0e0e0; padding: 12px 16px; text-align: left; white-space: nowrap; /* 核心!禁止换行,内容超长就滚动 */ overflow: hidden; /* 防止内容溢出单元格 */ text-overflow: ellipsis; /* 超长内容显示省略号 */ } /* 列宽微调:让关键列(如ID、操作)窄,名称列宽 */ th:nth-child(1), td:nth-child(1) { width: 100px; } th:nth-child(2), td:nth-child(2) { width: 200px; } th:nth-child(8), td:nth-child(8) { width: 120px; } /* 桌面端优化:去掉滚动条,用 hover 显示 */ @media (min-width: 768px) { .table-wrapper { overflow-x: visible; } .table-wrapper::-webkit-scrollbar { display: none; } }提示:
table-layout: fixed是灵魂。它让表格按第一行<th>的宽度定义列宽,后续行<td>内容再长,也不会撑开列宽,保证滚动体验稳定。没有它,white-space: nowrap可能导致整列无限拉宽。
步骤四:移动端真机测试,验证滚动流畅性
- 在 Chrome 开发者工具中切到 iPhone SE 模式,检查
.table-wrapper是否出现水平滚动条 - 用手指在表格上左右滑动,确认
-webkit-overflow-scrolling: touch生效(iOS 流畅) - 输入超长商品名(如“XX牌旗舰级超高清4K HDR智能电视AI语音遥控器套装”),确认显示为省略号,且可滚动查看完整内容
常见问题排查:
- 问题:滚动条不出现 →检查:
.table-wrapper是否有width: 100%?<table>是否有table-layout: fixed? - 问题:滚动卡顿 →检查:是否遗漏
-webkit-overflow-scrolling: touch?是否在<td>上用了box-shadow等重绘属性?