如何制作HTML数据对比面板区块?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1268个文字,预计阅读时间需要6分钟。
纯视觉对齐的对比面板看起来适合用于display: grid布局,但在实际开发中,语义错误、响应式断裂、屏幕阅读器支持差异等问题会集中爆发。真正要表达的是两组数据逐项对照,即table,是唯一符合语义且浏览器原生支持的方案。
关键不是“好不好看”,而是“能不能被正确解析”。比如价格对比、功能列表、配置差异,本质就是二维关系数据 —— 行是条目(如“存储空间”),列是方案(如“基础版”“专业版”)。
- 用
<table>+<thead>+<tbody>结构,首行用<th scope="col">标明方案名称 - 每项对比内容放在
<tr>里,用<th scope="row">标出条目名(如“并发连接数”),提升可访问性 - 避免给
table加width: 100%或强行等宽列 —— 让内容自然撑开,再用min-width控制最小列宽
移动端堆叠显示必须靠 @media + display: block 重排
直接在小屏上缩放 table 或加横向滚动,体验极差。必须主动切换布局:把“行→列”的表格结构,转成“条目标题 + 两个并列区块”的垂直流。
核心思路不是隐藏列,而是重构 DOM 渲染顺序。CSS 无法改变表格语义顺序,所以得靠媒体查询配合 display 变更:
立即学习“前端免费学习笔记(深入)”;
@media (max-width: 768px) { .comparison-table { display: block; } .comparison-table thead, .comparison-table tbody, .comparison-table tr, .comparison-table th, .comparison-table td { display: block; } .comparison-table th[scope="col"] { text-align: center; font-weight: bold; margin: 0.5em 0; } .comparison-table tr:not(:first-child) { margin-top: 1.5em; padding-top: 1em; border-top: 1px solid #eee; } }
- 不依赖
transform或position: absolute移动单元格位置 —— 这会让焦点顺序和读屏顺序错乱 - 每个
<tr>在小屏下变成一个独立区块,内部用flex或grid横向排两个值(需额外 wrapper,因为原td已设为block) - 务必测试 VoiceOver 和 TalkBack 下的朗读顺序:应为“条目名 → 方案A值 → 方案B值”,而非“方案A所有条目 → 方案B所有条目”
diff 类逻辑别在前端硬算,后端返回结构化差异字段
用户要的不是“左边红右边绿”,而是“哪几项变了、怎么变的、要不要关注”。如果让前端 JS 去比对两组 JSON 再渲染高亮,既增加首屏 JS 体积,又容易漏掉深层嵌套或浮点精度问题。
真实业务中,对比逻辑往往带业务规则:比如“价格变动超5%才标红”“配置项新增/删除需单独标记”“版本号按语义化比较”。这些不该由前端兜底。
- 后端接口返回的对比数据,应含
status: "added" | "removed" | "changed" | "same"字段,以及diffValue(变更后值)、oldValue(变更前值) - 前端只做映射渲染:
status === "changed"就加class="diff-changed",不参与计算 - 避免用
JSON.stringify(a) !== JSON.stringify(b)做浅比对 —— 对象属性顺序不同就会误判
可折叠条目要用 details/summary,别自己写 JS 展开收起
对比项多时(比如 API 参数表有 50+ 行),默认全展开会淹没重点。但手写 click + classList.toggle 不仅冗余,还破坏原生可访问性。
<details> 是 HTML5 原生可折叠组件,自带键盘支持(Enter/Space 切换)、状态感知(open 属性)、无需 JS 即可工作。
- 每个
<tr>外面包一层<details>,<summary>放条目名,内部用<div class="comparison-row">包两个值 - 用 CSS 选择器
details[open] summary::after控制箭头方向,比 JS 切 class 更可靠 - 不要给
<details>加role="region"或aria-expanded—— 它们已内置完整 ARIA 语义
复杂点在于服务端是否支持分段返回数据。如果整张表一次返回,前端用 <details> 没问题;但如果要懒加载子项(比如点击“安全配置”才拉取 20 条策略详情),就得退回到 JS 控制,此时注意用 aria-live="polite" 通知读屏器内容更新。
本文共计1268个文字,预计阅读时间需要6分钟。
纯视觉对齐的对比面板看起来适合用于display: grid布局,但在实际开发中,语义错误、响应式断裂、屏幕阅读器支持差异等问题会集中爆发。真正要表达的是两组数据逐项对照,即table,是唯一符合语义且浏览器原生支持的方案。
关键不是“好不好看”,而是“能不能被正确解析”。比如价格对比、功能列表、配置差异,本质就是二维关系数据 —— 行是条目(如“存储空间”),列是方案(如“基础版”“专业版”)。
- 用
<table>+<thead>+<tbody>结构,首行用<th scope="col">标明方案名称 - 每项对比内容放在
<tr>里,用<th scope="row">标出条目名(如“并发连接数”),提升可访问性 - 避免给
table加width: 100%或强行等宽列 —— 让内容自然撑开,再用min-width控制最小列宽
移动端堆叠显示必须靠 @media + display: block 重排
直接在小屏上缩放 table 或加横向滚动,体验极差。必须主动切换布局:把“行→列”的表格结构,转成“条目标题 + 两个并列区块”的垂直流。
核心思路不是隐藏列,而是重构 DOM 渲染顺序。CSS 无法改变表格语义顺序,所以得靠媒体查询配合 display 变更:
立即学习“前端免费学习笔记(深入)”;
@media (max-width: 768px) { .comparison-table { display: block; } .comparison-table thead, .comparison-table tbody, .comparison-table tr, .comparison-table th, .comparison-table td { display: block; } .comparison-table th[scope="col"] { text-align: center; font-weight: bold; margin: 0.5em 0; } .comparison-table tr:not(:first-child) { margin-top: 1.5em; padding-top: 1em; border-top: 1px solid #eee; } }
- 不依赖
transform或position: absolute移动单元格位置 —— 这会让焦点顺序和读屏顺序错乱 - 每个
<tr>在小屏下变成一个独立区块,内部用flex或grid横向排两个值(需额外 wrapper,因为原td已设为block) - 务必测试 VoiceOver 和 TalkBack 下的朗读顺序:应为“条目名 → 方案A值 → 方案B值”,而非“方案A所有条目 → 方案B所有条目”
diff 类逻辑别在前端硬算,后端返回结构化差异字段
用户要的不是“左边红右边绿”,而是“哪几项变了、怎么变的、要不要关注”。如果让前端 JS 去比对两组 JSON 再渲染高亮,既增加首屏 JS 体积,又容易漏掉深层嵌套或浮点精度问题。
真实业务中,对比逻辑往往带业务规则:比如“价格变动超5%才标红”“配置项新增/删除需单独标记”“版本号按语义化比较”。这些不该由前端兜底。
- 后端接口返回的对比数据,应含
status: "added" | "removed" | "changed" | "same"字段,以及diffValue(变更后值)、oldValue(变更前值) - 前端只做映射渲染:
status === "changed"就加class="diff-changed",不参与计算 - 避免用
JSON.stringify(a) !== JSON.stringify(b)做浅比对 —— 对象属性顺序不同就会误判
可折叠条目要用 details/summary,别自己写 JS 展开收起
对比项多时(比如 API 参数表有 50+ 行),默认全展开会淹没重点。但手写 click + classList.toggle 不仅冗余,还破坏原生可访问性。
<details> 是 HTML5 原生可折叠组件,自带键盘支持(Enter/Space 切换)、状态感知(open 属性)、无需 JS 即可工作。
- 每个
<tr>外面包一层<details>,<summary>放条目名,内部用<div class="comparison-row">包两个值 - 用 CSS 选择器
details[open] summary::after控制箭头方向,比 JS 切 class 更可靠 - 不要给
<details>加role="region"或aria-expanded—— 它们已内置完整 ARIA 语义
复杂点在于服务端是否支持分段返回数据。如果整张表一次返回,前端用 <details> 没问题;但如果要懒加载子项(比如点击“安全配置”才拉取 20 条策略详情),就得退回到 JS 控制,此时注意用 aria-live="polite" 通知读屏器内容更新。

