如何阻止下拉菜单子元素触发父元素的focusout事件?
事件监听继承问题导致下拉菜单故障
默认情况下,子元素会触发父元素的事件监听器——不管子元素是在监听器创建前还是后添加的,这完全不符合预期。我只想给父元素加监听器,不想让所有子元素都跟着触发,用stopPropagation和preventDefault解决太不优雅,而且还没法移除子元素触发父监听器的行为,这太糟了!
今天刚发现这个问题,直接搞砸了我的下拉菜单设计。
下拉菜单结构
- 父DIV元素
- 用于切换菜单的子DIV按钮
- 包裹选项的DIV容器(选项本身有独立监听器)
问题场景
本来只给切换按钮加点击监听器来切换菜单很简单,但我需要focusout事件实现点击菜单外区域自动关闭菜单的功能。给父DIV加focusout后,所有子元素都会触发这个事件,导致两个严重问题:
- 点击任意选项时,会触发两次
focusout,菜单切换两次(关了又开) - 菜单打开时点击切换按钮,
click和focusout会同时触发,同样导致菜单异常切换
代码实现
动态创建下拉菜单
var dropdown = document.createElement("div"); dropdown.setAttribute("tabindex", "0"); // 让DIV可以获得焦点 var toggle_button = document.createElement("div"); toggle_button.addEventListener("click", function() { toggleDropdown(dropdown); }); dropdown.addEventListener("focusout", function() { toggleDropdown(dropdown); }); dropdown.appendChild(toggle_button); document.body.appendChild(dropdown);
菜单切换函数
function toggleDropdown( dropdown ) { if ( dropdown.classList.contains("open") ) { dropdown.blur(); var options_wrap = dropdown.getElementsByClassName("options_wrap")[0]; dropdown.removeChild(options_wrap); dropdown.classList.remove("open"); } else { dropdown.focus(); var options_wrap = document.createElement("div"); options_wrap.setAttribute("class", "options_wrap"); var opt1 = document.createElement("div"); var opt2 = document.createElement("div"); opt1.addEventListener("click", function() { /* 执行特定操作 */ }); opt2.addEventListener("click", function() { /* 执行特定操作 */ }); options_wrap.appendChild(opt1); options_wrap.appendChild(opt2); dropdown.appendChild(options_wrap); dropdown.classList.add("open"); } }
尝试过的无效方案
- 用事件委托+捕获阶段监听,在监听器里调用
stopPropagation - 给事件添加
preventDefault - 检查事件
target并和触发元素做对比
核心需求
我就想禁用子元素触发父元素事件的默认行为,这种默认逻辑完全不合理,只会催生一堆临时解决方案,我非常反感这种凑活的办法。
内容的提问来源于stack exchange,提问作者Xavier
相关产品推荐
相关产品推荐

