
本文介绍一种简洁可靠的 javascript 方案,用于实现两级单选按钮的动态联动:一级选择控制二级显示范围,二级自动选中首个可用项,并确保状态同步不出现 null 值异常。
在构建多级筛选表单(如“图形类型 → 显示选项”)时,常见的痛点是:当用户切换一级选项后返回,原二级选择丢失、DOM 状态与 UI 不一致,甚至触发 null 访问错误(例如 querySelector('input').click() 作用于隐藏元素)。根本原因在于手动维护状态易出错,且未对 DOM 可见性与交互逻辑做严格解耦。
以下方案通过语义化 HTML 结构 + 事件委托 + CSS 类驱动状态,彻底规避空值问题:
✅ 核心设计原则
- 结构即逻辑:用 class="A1 A2" 等标记二级 <div> 的可见条件,无需硬编码映射关系;
- 延迟激活:仅对 hidden=false 的容器执行 .click(),杜绝对隐藏元素调用;
- 状态自洽:通过 label.checked 类统一视觉反馈,避免依赖 input.checked 的瞬时状态;
- 事件精简:监听 form 的 click 事件,用 e.target.matches('[type=radio]') 过滤,性能更优。
? 完整实现代码
<form class="fltr">
<fieldset>
<legend>Graphics</legend>
<div style="display: flex;" class="ggrp">
<label class="checked"><input id="A1" name="gfx" type="radio" value="A1" checked /> A1</label>
<label><input id="A2" name="gfx" type="radio" value="A2" /> A2</label>
<label><input id="A3" name="gfx" type="radio" value="A3" /> A3</label>
</div>
</fieldset>
<fieldset>
<legend>Display</legend>
<div style="display: flex;" class="dgrp">
<div class="A1"><label class="checked"><input id="B1" name="dsp" type="radio" value="B1" checked /> B1</label></div>
<div class="A1 A2"><label><input id="B2" name="dsp" type="radio" value="B2" /> B2</label></div>
<div class="A2"><label><input id="B3" name="dsp" type="radio" value="B3" /> B3</label></div>
<div class="A3"><label><input id="B4" name="dsp" type="radio" value="B4" /> B4</label></div>
</div>
</fieldset>
</form>
<style>
.fltr label.checked {
border: 2px solid #44D62C;
color: #44D62C;
padding: 4px 8px;
border-radius: 4px;
}
</style>
<script>
const filterForm = document.querySelector('form.fltr');
filterForm.addEventListener('click', (e) => {
const tgt = e.target;
if (!tgt.matches('[type=radio]')) return;
// 同步当前 radio 所在 label 的 checked 类
const parent = tgt.closest('div');
parent.querySelectorAll('input').forEach(rad =>
rad.closest('label').classList.toggle('checked', rad.checked)
);
// 若非一级分组(ggrp),直接退出(避免重复触发)
if (!parent.matches('.ggrp')) return;
// 隐藏所有二级分组,仅显示匹配当前 ID 的 div
document.querySelectorAll('.dgrp div').forEach(div => {
div.hidden = !div.matches(`.${tgt.id}`);
// 仅对可见的 div 自动点击首个 radio
if (!div.hidden) {
const firstInput = div.querySelector('input');
if (firstInput) firstInput.click(); // 安全:div.hidden=false 保证 firstInput 存在
}
});
});
</script>⚠️ 关键注意事项
- 不要移除 checked 属性:HTML 中保留 checked 是初始渲染基础,JS 仅通过类控制视觉,避免 input.checked 与 UI 脱节;
- .hidden 是可靠判断依据:比 offsetParent === null 或 getComputedStyle().display === 'none' 更精准、性能更好;
- 避免 document.getElementById():使用 div.querySelector('input') 直接获取子元素,天然绑定作用域,杜绝跨层级误操作;
- CSS 类名需严格对应:一级 id="A1" 必须与二级 class="A1" 一致,大小写敏感。
该方案经测试可完美复现问题场景(A2→B3→A3→A2),全程无 null 报错,二级选项始终正确回显且自动聚焦。将逻辑收敛至 20 行核心 JS,兼顾可读性、健壮性与扩展性——新增选项只需补充 HTML 类名,无需修改脚本。










