从“点击别处关闭浮层”了解事件传播机制
写出一个点开浮层、关闭浮层的例子,要求:
1)点击按钮弹出浮层
2)点击别处关闭浮层
3)点击浮层时,浮层不得关闭
4)再次点击按钮,浮层消失
第一次尝试:
(1)监听 body
新手最容易想到的方式是,监听 body
,如下图:
然而,此时点击按钮并不会弹出浮层,却能正常输出 'block'
和 'none'
。并且由于 button
父元素 wrapper
的高度并不是全屏幕,因此点击屏幕其他空白处,因为 body
监听不到点击事件,连 none
都无法打印。
(2)监听 document
那么换成监听 document
,即整个页面后,会成功吗?
同样也是无法弹出浮层的。
这是因为,事件在冒泡过程中,冒泡顺序是由子到父的顺序,即先监听到了 button
的 click
事件,接着马上监听到了 document
的 click
事件,浮层确实有弹出过,但是立马又被 display: none;
隐藏了。
(3)stopPropagation()
那么如何解决被连续监听呢,首先想到的答案是阻止事件冒泡,即 stopPropagation()
方法,详见代码。如下图:
(4)监听按钮父元素
似乎功能实现了,但是此时点击浮层自身,浮层也会消失。因此如果浮层内部还有内容需要操作时,则应当在 wapper
上阻止冒泡,而不是 button
。
这是终极答案了么?
第二次尝试
上面的方法中,每次点击屏幕都会触发 document
的点击事件,当按钮非常多,且有许多无意识点击屏幕空白处的时候,这种方法就非常消耗内存了。
(1)一次监听 one()
在按钮的监听事件中,阻止冒泡,并且使用 one()
方法做到只有当按钮被点击之后 document
才添加对点击事件的的一次监听,document
对于其他点击事件不监听,以节省内存。
注意:如果在 checkbox
父元素的任何一层(包括 checkbox
自己)添加了 preventDefault()
方法,那么 checkbox
将无法被 check。如下:
(2)延迟函数
思考上面(1)一次监听中的代码,如果不阻止冒泡,应该怎么做呢?
答案是用延迟函数 setTimeout
,这样做会在冒泡阶段结束后再添加 one click
监听函数。
下图为整个浮层 show()
到 hide()
的整个过程,也可以验证函数的执行顺序。
第三次尝试
上面的代码,有两个bug:
- 虽然延迟函数能代替阻止冒泡实现
show()
之后不立即hide()
,但是点击浮层自身仍然会隐藏 - 如果只点击按钮,那么点击两次之后,因为没有对按钮做判断,将无法再显示出浮层
最终改良代码:
$(clickMe).on('click', function() {
if (popover.style.display == 'block') {
$(popover).hide()
} else {
$(popover).show()
$(document).one('click', function() {
$(popover).hide()
})
}
})
$(wrapper).on('click', function(e) {
e.stopPropagation()
})
新思路(待填坑)
遮罩层
在弹窗和所有页面其他内容之间放一个透明层,点那个层的时候关闭。
stack overflow 思路
$(document).mouseup(function(e){
var _con = $(' 目标区域 '); // 设置目标区域
if(!_con.is(e.target) && _con.has(e.target).length === 0){ // Mark 1
some code... // 功能代码
}
});
/* Mark 1 的原理:
判断点击事件发生在区域外的条件是:
1. 点击事件的对象不是目标区域本身
2. 事件对象同时也不是目标区域的子元素
*/