问题引入
假设有这样一个功能需求,input框聚焦时,框下方列表会显示,而失焦时列表会隐藏,列表点击时应拿到对应列表项内容,然后进行对于输入框的渲染或其他操作。代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
ul{
display: none;
}
</style>
</head>
<body>
<input type="text" id="ipt">
<ul id="ul">
<li>111</li>
<li>222</li>
<li>333</li>
</ul>
</body>
<script>
ipt.onfocus=()=>{
ul.style.display='block'
}
ipt.onblur=()=>{
ul.style.display='none'
}
ul.onclick=(e)=>{
ipt.value=e.target.innerText
}
</script>
</html>
上述代码会产生这样一个bug:当ul的点击事件触发时,会先触发失焦事件,让ul隐藏,然后再触发点击事件,而此时点击拿不到ul中的li数据,这就好比在某人坐下瞬间抽掉板凳一样。
问题解决
面对上述问题,有以下三种解决方式:
法1:将点击触发改为按下时触发
如果改成按下时就触发,那么事件发生过程为触发按下事件然后才触发失焦事件,代码改为:
//不变
ipt.onblur=()=>{
setTimeOut(()=>{
ul.style.display='none'
},100)
}
ul.onmousedown=(e)=>{
ipt.value=e.target.innerText
}
法2:将失焦事件延迟触发
如果将失焦事件添加延迟效果,那么事件触发过程就是在失焦延迟的过程中触发点击事件,延迟结束后点击事件已经运行完成,再触发失焦效果。代码修改如下:
ipt.onblur=()=>{
setTimeOut(()=>{
ul.style.display='none'
},100)
}
//不变
ul.onclick=(e)=>{
ipt.value=e.target.innerText
}
法3:监听document的冒泡事件
这种方法是利用事件冒泡,判断点击事件的触发源,控制ul的显示或隐藏的同时获得对应li的值
let html=document.querySelector('html')
html.onclick=(e)=>{
if(e.target.nodeName!=='INPUT'){
console.log(e.target.nodeName)
ul.style.display='none';
}
}
//不变
ul.onclick=(e)=>{
ipt.value=e.target.innerText
}
//不需要设置失焦事件