需求:
- 点击一个按钮,它弹出一个浮层。
- 浮层上面可以作点、选、输入、确定等操作。其中一些操作可以引起浮层关闭。
- 点击浮层外部时,该浮层关闭。
实现
一、弹出浮层
状态里存个show: false。
点击按钮切换这个show的值。
当show===true时,显示浮层。
二、主动操作 · 关闭浮层
需要关闭时,让show = false。
三、点击浮层外部,关闭浮层
关键点就在这里。我们不知道什么时候用户点击了浮层的外部。
如果用监听window的click事件冒泡,有可能现在,或是将来,被一些阻止冒泡的元素截断该事件。
所以我采用了聚焦/失焦。
<div tabindex="0" class="box">
<div>触发按钮</div>
<div>浮层</div>
</div>
现在监听.box元素,如果该元素blur(失焦)了,show = false,浮层关闭。
因为浮层在.box元素里,所以点击浮层外部时,即触发该元素的blur。
目前为止,如果你在浮层里面只放了div等不会focus(聚焦)的元素,它已足以工作。这可以用来制造一个简单的select组件。
问题
抢焦
依据上文第三步,可知,失焦时会导致关浮层。
假如在.box元素里,放置了会focus的元素input,那么当点击input使之focus时,会触发.box的blur,使得产生 点击浮层内input时,关闭了浮层 的不合理效果。
解决思路:冒泡思路。在监听.box的blur事件时,如果该事件来自子元素,那就不关闭浮层。但这用冒泡技术做不到,因为focus、blur事件不冒泡。
现在的重点是,要在blur时,知道这事是不是子孙干的。
有两样东西可以做到这一点
document.activeElement。获取当前focus的dom元素domA.contains(domB)。检查domB是不是domA的子孙
所以,只要在blur时,检查一下当前网页focus的dom元素是不是.box的子孙,就行了。
但是有一个坏消息,事件顺序在chrome浏览器里如此发生:blur -> focus。也就是说,还来不及focus子孙,就已经先blur了,导致浮层提前关闭,而因为浮层关闭,导致input跟着消失,也就无法再被focus了。
解决思路:调整顺序,让focus,发生得比blur的监听函数早。
事件顺序是板上钉钉的事情,无法改变。但blur的监听函数,可以做点手脚。
setTimeout,把它包在blur监听函数的函数体上,也就是blur监听函数的业务代码上。
function handleBlur(){
setTimeout(()=>{
if(聚焦的是当前元素的儿子) return;
show = false
})
}
于是,达到了调整顺序效果。该问题解决。
至此,Popover弹出框,安全实现。