应放弃select[multiple],改用div+checkbox模拟多重筛选菜单,因其支持搜索、全选、依赖联动、语义化及无障碍访问,且兼容iOS与桌面端。

用 select[multiple] 做多重筛选?先看清限制
原生 select 加 multiple 属性确实能实现多选,但它不是“多重下拉筛选菜单”的合理起点——它默认渲染为多行列表(哪怕设了 size="1"),iOS Safari 完全不支持多选,且无法控制子项分组、搜索、异步加载或视觉联动。真要用于筛选场景,90% 的情况该放弃它。
-
select[multiple]提交时字段名会重复(如category=1&category=2),后端需按数组接收,但前端 JS 读值必须用select.selectedOptions,select.value只返回第一个 - 样式几乎不可控:Chrome/Firefox 对
multiple的滚动条、高度、聚焦态渲染不一致,CSS 伪元素(如::after)对它无效 - 没有搜索框、无“全选/反选”按钮、无法高亮已选、不能动态禁用依赖项(比如选了“硬件”后,“软件”选项自动灰掉)
真正可用的多重筛选菜单结构:用 div + checkbox 模拟
桌面端和移动端都稳定、可键盘导航、能加搜索、支持依赖逻辑——核心是放弃 select,改用语义化但可控的结构:
<div class="filter-group" data-filter="category">
<h3>分类</h3>
<div class="filter-search"><input type="text" placeholder="搜索..."></div>
<div class="filter-options">
<label><input type="checkbox" value="hardware"> 硬件</label>
<label><input type="checkbox" value="software"> 软件</label>
<label><input type="checkbox" value="service"> 服务</label>
</div>
<button type="button" class="select-all">全选</button>
</div>
- 每个
filter-group对应一个筛选维度,data-filter用于 JS 绑定逻辑 -
input[type="checkbox"]必须包裹在label里,否则点击文字无法触发,且屏幕阅读器会忽略 - 搜索框监听
input事件过滤.filter-options label的显隐,别用模糊匹配——性能差,直接用textContent.includes()即可 - “全选”按钮不要用
checked=true批量设属性,而应调用checkbox.click()触发原生事件,确保绑定的change监听器能响应
多个筛选维度联动时,如何避免状态混乱
当用户在“品牌”里选了 Apple,在“价格区间”里选了 5000–10000,再切回“品牌”发现 Samsung 也被勾上了——这是没隔离各维度状态导致的。关键不是存数据,而是清空时机:
- 每次点击某个
filter-group外部区域(包括其他筛选区、空白处、提交按钮),应只收起当前展开的面板,**不重置已选项** - 只有点击“重置筛选”按钮时,才遍历所有
input[type="checkbox"]并调用.click()(不是.checked = false) - 若某维度依赖另一维度(如“型号”列表随“品牌”变化),不要在
change里直接替换.filter-options.innerHTML——这会丢失所有 checkbox 的事件监听器;正确做法是先removeChild旧节点,再用document.createElement重建并重新绑定 - 筛选结果实时更新时,用
URLSearchParams同步到地址栏 hash 或 search 参数,方便分享和刷新保留,但注意避免高频写入触发浏览器 history flood
移动端点击展开/收起菜单的最小可靠逻辑
纯 CSS :hover 在手机上无效,且 focus 不稳定。必须用 JS 控制显隐,但不用复杂框架:
立即学习“前端免费学习笔记(深入)”;
- 给每个
.filter-group h3绑click事件,点击时切换.filter-options的max-height(从0到300px)+overflow(hidden→visible),配合 CSS 过渡实现展开动画 - 禁止在
touchstart里阻止默认行为(e.preventDefault()),否则会导致 iOS 上 input 聚焦失灵 - 用
document.addEventListener('click', e => { ... })捕获全局点击,判断e.target.closest('.filter-group')是否为空,为空则收起所有展开项 - 键盘用户需要支持
Enter和Space键触发展开,直接监听keydown并检查e.key === 'Enter' || e.key === ' '
最易被忽略的是:所有交互操作后,必须同步更新 aria-expanded 和 aria-controls 属性——否则视障用户根本不知道哪个筛选区打开了,也点不开。



















