原生 无展开事件,仅支持 change、focus、blur;需监听展开/收起时应改用 组件并监听 toggle 事件,或选用 react-select 等可控方案。

click 事件绑定在 <select></select> 上根本没用
浏览器对 <select></select> 的展开行为做了原生拦截,直接给它绑 click 或 mousedown 事件,绝大多数情况下收不到——哪怕加了 preventDefault() 也拦不住原生下拉逻辑。这不是 bug,是规范行为。
真正能响应的只有两个时机:change(选中后触发)和 focus/blur(获得/失去焦点),但它们都不等于“下拉展开”这个动作。
- 用户点击箭头或空白区域展开菜单 → 无对应 DOM 事件
- 用户按
Alt+↓或空格键展开 → 同样无标准事件捕获 -
change只在选项被选中且焦点离开时才触发,无法区分“展开→取消”和“展开→选中”
用 input + details 模拟可监听的下拉菜单
如果必须监听“展开/收起”状态,唯一可靠方式是放弃原生 <select></select>,改用语义化、可完全控制的组合:
<details>
<summary>请选择</summary>
<div class="dropdown-menu">
<button data-value="a">选项 A</button>
<button data-value="b">选项 B</button>
</div>
</details>
然后监听 toggle 事件:
立即学习“前端免费学习笔记(深入)”;
document.querySelector('details').addEventListener('toggle', (e) => {
console.log('展开状态:', e.target.open); // true = 展开,false = 收起
});
-
<details></details>原生支持toggle事件,且兼容 Chrome/Firefox/Safari(Edge 79+) - 需配合 CSS 隐藏默认箭头、自定义样式;
<summary></summary>点击即触发 toggle - 注意:Safari 旧版(≤15.4)中
toggle在页面加载时可能误触发一次,建议加if (e.target.offsetParent)过滤
想劫持原生 <select></select> 的展开?只能靠 hack 检测视觉变化
极少数场景(如埋点统计“用户是否尝试展开”)需要近似判断,可行但不稳定:
- 监听
focus+ 定时检查offsetHeight是否突增(部分浏览器展开时 select 高度会变) - 用
ResizeObserver监听父容器尺寸抖动(不推荐,误报率高) - 结合
mousedown+mousemove判断鼠标是否落在 select 右侧 20px 区域(模拟箭头点击热区)
这些都不是真正的“展开事件”,只是启发式猜测。一旦浏览器更新渲染策略或用户使用键盘操作,就失效。
React/Vue 中的常见误区:别在 onChange 里写“展开后逻辑”
框架开发者常误以为 onChange 能代表“菜单打开”,结果发现:
- React 的
onChange仅在值变更后触发,首次点击展开不触发 - Vue 的
@change同理,且v-model绑定后更难插入中间逻辑 - 试图用
ref+useEffect监听open属性?原生<select></select>根本没有该属性
结论很直接:只要需求明确要“监听展开动作”,就必须换方案。用 <details></details>、react-select、@headlessui/react 这类可控组件,而不是硬刚原生 <select></select> 的事件盲区。
原生下拉菜单的“展开”本质上不是 Web API 暴露的行为,而是浏览器 UI 层私有操作。想监听它,就像想监听系统托盘弹窗的显示一样,得绕开窗口句柄,自己画个托盘。



















