onfocusout事件隐藏下拉项导致按钮onclick逻辑无法执行问题咨询
问题根因
浏览器事件触发遵循固定顺序:鼠标点击下拉选项时,会先触发当前聚焦元素(文本输入框)的focusout失焦事件,之后才会触发被点击按钮的click事件。直接在失焦回调里把下拉容器设为display: none,会让按钮立刻从渲染树中移除,后续的点击事件找不到目标元素,选值逻辑自然无法执行。
可行解决方案
以下三种方案均可解决事件执行顺序冲突问题,按需选用即可:
方案1:给隐藏逻辑加短延迟(实现成本最低)
用setTimeout把隐藏下拉的逻辑放到宏任务队列,等当前事件循环内的按钮点击事件执行完成后,再运行隐藏逻辑。选值完成后记得清除定时器,避免点完选项下拉延迟消失的体验问题。
修改后的JS代码:
let hideTimer = null function showNames() { // 显示下拉时清除残留的隐藏定时器 if(hideTimer) clearTimeout(hideTimer) document.getElementById("dropdown_names").style.display = 'block' } function hideNames() { // 加50ms短延迟即可,人眼无感知 hideTimer = setTimeout(() => { document.getElementById("dropdown_names").style.display = 'none' }, 50) } function selectName(name) { // 选值时清除定时器,选完立刻隐藏 if(hideTimer) clearTimeout(hideTimer) document.getElementById("name_inp").value = name hideNames() }
*注意:原有代码存在两处笔误会影响功能:
- 第三个下拉按钮绑定的事件为
selecttName(多写了一个t),会触发函数未定义报错,需要改成selectName('evgen') - CSS中聚焦样式选择器写的是
#resident_name_inp:focus,和输入框实际idname_inp不匹配,样式不会生效*
方案2:阻止下拉区域的mousedown默认行为(无延迟)
鼠标点击时的mousedown事件触发时机早于输入框的focusout事件,在下拉容器上监听mousedown事件并调用preventDefault(),可以阻止输入框失焦,也就不会提前触发隐藏逻辑,按钮的click事件可以正常执行。
新增JS代码:
document.addEventListener('DOMContentLoaded', () => { const ddContent = document.getElementById("dropdown_names") ddContent.addEventListener('mousedown', (e) => { // 仅阻止默认的焦点转移行为,不阻止事件冒泡 e.preventDefault() }) })
原有逻辑无需大改,只需要在selectName方法选值完成后主动调用hideNames()即可。
方案3:失焦时判断焦点去向,过滤下拉内点击
focusout事件自带relatedTarget属性,指向失焦后新获得焦点的元素。如果新焦点在下拉容器内部,就暂时不执行隐藏逻辑,等选值完成后再手动隐藏。
修改后的hideNames方法:
function hideNames(e) { const ddContent = document.getElementById("dropdown_names") // 失焦后焦点落在下拉容器内部时,暂不隐藏 if (e.relatedTarget && ddContent.contains(e.relatedTarget)) { return } ddContent.style.display = 'none' }
对应的输入框标签需要修改,把事件对象传入回调:
<input id="name_inp" type="text" placeholder="Search Names..." onfocus="showNames()" onfocusout="hideNames(event)">
同样在selectName选值完成后主动调用hideNames()即可。
内容的提问来源于stack exchange,提问作者Andrey Kachow
相关产品推荐
相关产品推荐

