用 table 做对比面板最稳,因其语义正确、可访问性强、响应式可控;需配合 thead/tbody、scope 属性、媒体查询重构布局、后端返回结构化 diff 数据及原生 details/summary 实现可折叠。

用 table 做对比面板最稳,别硬套 CSS Grid
纯视觉对齐的“对比面板”看似适合用 display: grid,但实际开发中,语义错位、响应式断裂、屏幕阅读器支持差的问题会集中爆发。真正要表达“两组数据逐项对照”,table 是唯一符合语义且浏览器原生支持的方案。
关键不是“好不好看”,而是“能不能被正确解析”。比如价格对比、功能列表、配置差异,本质就是二维关系数据 —— 行是条目(如“存储空间”),列是方案(如“基础版”“专业版”)。
- 用
<table> + <code><thead> + <code><tbody> 结构,首行用 <code><th scope="col"> 标明方案名称 <li>每项对比内容放在 <code><tr> 里,用 <code><th scope="row"> 标出条目名(如“并发连接数”),提升可访问性 <li>避免给 <code>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> 在小屏下变成一个独立区块,内部用 <code>flex或grid横向排两个值(需额外 wrapper,因为原td已设为block) - 务必测试 VoiceOver 和 TalkBack 下的朗读顺序:应为“条目名 → 方案A值 → 方案B值”,而非“方案A所有条目 → 方案B所有条目”
- 后端接口返回的对比数据,应含
status: "added" | "removed" | "changed" | "same"字段,以及diffValue(变更后值)、oldValue(变更前值) - 前端只做映射渲染:
status === "changed"就加class="diff-changed",不参与计算 - 避免用
JSON.stringify(a) !== JSON.stringify(b)做浅比对 —— 对象属性顺序不同就会误判 - 每个
<tr> 外面包一层 <code><details></details>,<summary></summary>放条目名,内部用<div class="comparison-row"> 包两个值 <li>用 CSS 选择器 <code>details[open] summary::after控制箭头方向,比 JS 切 class 更可靠 - 不要给
<details></details>加role="region"或aria-expanded—— 它们已内置完整 ARIA 语义
diff 类逻辑别在前端硬算,后端返回结构化差异字段
用户要的不是“左边红右边绿”,而是“哪几项变了、怎么变的、要不要关注”。如果让前端 JS 去比对两组 JSON 再渲染高亮,既增加首屏 JS 体积,又容易漏掉深层嵌套或浮点精度问题。
真实业务中,对比逻辑往往带业务规则:比如“价格变动超5%才标红”“配置项新增/删除需单独标记”“版本号按语义化比较”。这些不该由前端兜底。
可折叠条目要用 details/summary,别自己写 JS 展开收起
对比项多时(比如 API 参数表有 50+ 行),默认全展开会淹没重点。但手写 click + classList.toggle 不仅冗余,还破坏原生可访问性。
<details></details> 是 HTML5 原生可折叠组件,自带键盘支持(Enter/Space 切换)、状态感知(open 属性)、无需 JS 即可工作。
复杂点在于服务端是否支持分段返回数据。如果整张表一次返回,前端用 <details></details> 没问题;但如果要懒加载子项(比如点击“安全配置”才拉取 20 条策略详情),就得退回到 JS 控制,此时注意用 aria-live="polite" 通知读屏器内容更新。











