原生 无法实现点击展开、带勾选图标、支持搜索的下拉复选框,因其仅为固定滚动多选列表,无下拉交互、不可样式化、跨平台兼容差;必须用 + 模拟,配合 js 控制显隐与状态同步。

原生 <select multiple></select> 不是“下拉复选框”,它只是多选列表,没有展开/收起交互,也不带勾选图标;真要实现点击展开、可打钩、支持搜索的下拉复选框,必须用 <div> + <code><input type="checkbox"> 模拟,配合 JS 控制显隐和状态同步。
为什么不能直接用 <select multiple></select> 做“下拉复选框”
浏览器对 <select multiple></select> 的渲染是固定滚动列表,不是“点击才展开”的下拉行为。它没有视觉上的“下拉箭头”,不显示勾选状态(仅高亮),且:
- iOS Safari 完全忽略
multiple属性,点开会弹出单选原生 Picker - Android 各厂商浏览器行为不一致,多数不支持多选或无反馈
- CSS 几乎无法定制:
appearance: none失效,不能加复选框图标,滚动条样式不可靠 - 用户需按 Ctrl/Cmd 才能多选,桌面端也容易误操作——这不是“复选框”,是“多选列表”
<div> + <code><input type="checkbox"> 模拟下拉复选框的关键结构
核心是三层容器:触发区(显示已选项)、遮罩层(防止点透)、选项区(绝对定位的 checkbox 列表)。示例结构如下:
<div class="dropdown-checkbox">
<button type="button" class="dropdown-toggle">已选:0 项</button>
<div class="dropdown-menu">
<label><input type="checkbox" value="html"> HTML</label>
<label><input type="checkbox" value="css"> CSS</label>
<label><input type="checkbox" value="js"> JavaScript</label>
</div>
</div>
要点:
- 每个
<input type="checkbox">必须包裹在<label></label>中,否则点击文字无法切换状态 - 触发按钮不要用
<input>或<select></select>,避免语义冲突和聚焦干扰 -
.dropdown-menu需设position: absolute,父容器.dropdown-checkbox设position: relative - 用 JS 控制
.dropdown-menu的display或visibility,别依赖:hover(移动端失效)
JS 同步选中状态与更新触发按钮文本
不能只监听 click,得用 change 事件捕获所有变更(包括键盘空格、辅助工具):
const dropdown = document.querySelector('.dropdown-checkbox');
const toggle = dropdown.querySelector('.dropdown-toggle');
const checkboxes = dropdown.querySelectorAll('input[type="checkbox"]');
// 更新按钮文本
function updateToggleText() {
const checked = Array.from(checkboxes).filter(cb => cb.checked);
toggle.textContent = `已选:${checked.length} 项`;
}
// 绑定 change 事件(注意:用事件委托更优,尤其动态增删选项时)
checkboxes.forEach(cb => cb.addEventListener('change', updateToggleText));
// 点击按钮展开/收起菜单
toggle.addEventListener('click', () => {
const menu = dropdown.querySelector('.dropdown-menu');
menu.style.display = menu.style.display === 'block' ? 'none' : 'block';
});
// 点击外部关闭
document.addEventListener('click', e => {
if (!dropdown.contains(e.target)) {
dropdown.querySelector('.dropdown-menu').style.display = 'none';
}
});
容易踩的坑:
- 用
setAttribute('checked', '')设置状态无效——必须用cb.checked = true - 没阻止事件冒泡,导致点 checkbox 后菜单立刻关闭(在 checkbox 的 change 回调里加
e.stopPropagation()) - 没处理 indeterminate 状态:全选框本身如果是独立控件,需手动设置
indeterminate = true表示“部分选中” - 移动端未扩大点击区域,
<label></label>是必须的,别省略
移动端适配和性能提醒
真实项目里最容易被跳过的其实是这两点:
- iOS 上
position: absolute菜单可能被软键盘顶起或截断,建议加max-height: 200px; overflow-y: auto并确保父容器有明确高度上下文 - 选项超过 50 个时,
querySelectorAll+ 循环绑定change会卡顿,改用事件委托:dropdown.addEventListener('change', e => { if (e.target.type === 'checkbox') updateToggleText(); }) - 如果选项需要搜索过滤,别在 DOM 中反复
show/hide,用filter()生成新数组再重绘列表(或用display: none切换 class,CSS 触发 GPU 加速) - 别在
change回调里直接调后端接口——用户连点两下,可能发两次请求;加防抖或合并提交逻辑











