如何实现input失焦隐藏下拉前先触发子元素的点击事件
问题根因
问题来自两方面原因:
- CSS的下拉展开逻辑完全绑定在input的聚焦状态上:只要input触发blur失焦,相邻的
.child-2容器就会从max-height:500px缩回max-height:0。浏览器原生事件时序中,mousedown先于click触发,点击下拉选项时首先会让input失焦,下拉容器会在click事件完整触发前就隐藏到可视范围外,导致点击根本无法落到grandchild元素上,也就是感知到的“失焦先于点击执行”。 - 原生JS代码本身存在逻辑bug,即使解决失焦问题,原代码也无法正常运行:
- 文本匹配时判断对象是整个NodeList的
innerHTML,不是单个grandchild的文本内容,所有选项的显示隐藏逻辑都会出错 - 每次keyup触发时都给grandchild重复绑定click事件,会造成事件监听器叠加
- 循环内绑定的click回调直接引用循环变量
x,存在闭包取值问题,即使绑定成功也会出现内容取值错误
解决方案
核心处理:避免input提前失焦
最稳定的做法是给整个下拉容器绑定mousedown事件,在回调中调用e.preventDefault()。这个操作会阻止浏览器在mousedown阶段自动触发input的失焦流程,等下拉选项的完整click事件执行完毕后,再处理后续的状态切换。
如果需要保留失焦后自动收起下拉的逻辑,可以把失焦的状态判断放在100-200ms的定时器延迟中执行,给点击事件留足触发时间。
配套修复原有代码的逻辑问题
- 不要在keyup的循环逻辑里重复绑定事件,把grandchild的click监听器提到外部,只做一次绑定
- 过滤判断时逐个遍历每个grandchild的文本内容做匹配,不要对整个NodeList做判断
- 用
forEach循环或者let声明循环变量,避免闭包取值错误
修正后的可运行核心代码
// 文本包含判断工具 function isContainText(source, target) { return source.toLowerCase().includes(target.toLowerCase()); } const fieldInput = document.querySelector(".child-1"); const dropdown = document.querySelector(".child-2"); const optionItems = document.querySelectorAll(".grandchild"); // 仅绑定一次选项点击事件 optionItems.forEach(item => { item.addEventListener("click", function() { console.log(this.textContent.trim()); }); }); // 阻止下拉容器区域的默认失焦行为 dropdown.addEventListener("mousedown", e => { e.preventDefault(); }); // 输入过滤逻辑 fieldInput.addEventListener("keyup", function() { const keyword = this.value.trim(); optionItems.forEach(item => { const match = isContainText(item.textContent, keyword); item.style.display = match ? "block" : "none"; }); });
内容的提问来源于stack exchange,提问作者Irvan Hilmi
相关产品推荐
相关产品推荐

