直接用display:none切换显隐最常见但易崩,因nextSibling定位不准、data-level缺失致父子关系丢失、visibility:hidden占位、状态与图标不同步、aria-expanded缺失影响可访问性。

直接用 display: none 切换 <tr> 显隐是最常见做法,但错在 DOM 定位不准、层级混乱、可访问性缺失——不是“能不能”,而是“怎么切才不崩”。
<h3>为什么 nextSibling 找不到目标行?</h3>
<p>浏览器解析 HTML 时会自动剥离文本节点(比如换行、空格),导致 <code>nextSibling 返回的是文本节点而非 <tr>。一旦中间有回车或注释,<code>el.nextSibling 就失效。
- 必须用
nextElementSibling,它只认元素节点,跳过所有空白和注释 - 确保结构严格:触发行
<tr> → 紧跟一个 <code><tr class="detail-row">,中间不能插任何非元素内容 <li>如果用了 <code><tbody> 包裹数据行,得先确认目标行是否在同一 <code><tbody> 下,跨组时 <code>nextElementSibling会返回nulldata-level 怎么避免父子关系丢失?
<tr> 不允许嵌套,强行写 <code><tr></tr> <tr>... 会被浏览器自动“拍平”到同级,DOM 树里根本不存在父子路径。靠位置推断层级纯属碰运气。 <ul> <li>每行必须带 <code>data-level="1"、data-level="2",并配data-parent-id="row-101"指向父行 ID - 展开 level-1 行时,查
document.querySelectorAll('tr[data-level="2"][data-parent-id="row-101"]'),而不是tr[data-level="2"] - 别用
visibility: hidden:它占布局空间,折叠后仍影响滚动条高度和点击热区
箭头图标旋转和状态怎么保持同步?
图标方向和行显隐不同步,是用户最困惑的点——点了 ▶ 却没展开,或已收起但图标还是 ▼。根源在于状态没绑定到同一控制源。
立即学习“前端免费学习笔记(深入)”;
- 不要单独操作
icon.classList和row.style.display,改用统一状态标记:row.setAttribute('data-expanded', 'true') - CSS 里用属性选择器控制旋转:
tr[data-expanded="true"] .arrow { transform: rotate(90deg); } - 初始状态全显,JS 启动时再批量设
data-expanded="false",避免 HTML 里硬写style="display:none"干扰动态增删
aria-expanded 为什么不能省?
屏幕阅读器靠 aria-expanded 判断当前是否可展开,没有它,视障用户完全不知道那行还能点、点了会发生什么。这不是“锦上添花”,是合规底线。
- 每次切换显隐,必须同步更新:
icon.setAttribute('aria-expanded', 'true')和targetRow.setAttribute('aria-hidden', 'false') - 触发元素需加
role="button",否则键盘按Enter或Space不响应 - 别把事件绑在整行
<tr> 上——容易误触,应限定在带 <code>role="button"的<td> 或 <code><span></span>内真正难的不是让行消失,而是让消失这件事对 DOM 结构、键盘操作、屏幕阅读器、后续 JS 查询都“透明且可控”。多一级嵌套,就要多一层
data-parent-id过滤;少一个aria-expanded,就等于关掉了一半用户的入口。



















