饥人谷技术博客Web前端之路

从“点击别处关闭浮层”了解事件传播机制

2019-05-01  本文已影响68人  许骁Charles

写出一个点开浮层、关闭浮层的例子,要求:
1)点击按钮弹出浮层
2)点击别处关闭浮层
3)点击浮层时,浮层不得关闭
4)再次点击按钮,浮层消失

第一次尝试:

(1)监听 body

新手最容易想到的方式是,监听 body,如下图:

然而,此时点击按钮并不会弹出浮层,却能正常输出 'block''none'。并且由于 button 父元素 wrapper 的高度并不是全屏幕,因此点击屏幕其他空白处,因为 body 监听不到点击事件,连 none 都无法打印。

(2)监听 document

那么换成监听 document,即整个页面后,会成功吗?

同样也是无法弹出浮层的。

这是因为,事件在冒泡过程中,冒泡顺序是由子到父的顺序,即先监听到了 buttonclick 事件,接着马上监听到了 documentclick 事件,浮层确实有弹出过,但是立马又被 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:

  1. 虽然延迟函数能代替阻止冒泡实现 show() 之后不立即 hide() ,但是点击浮层自身仍然会隐藏
  2. 如果只点击按钮,那么点击两次之后,因为没有对按钮做判断,将无法再显示出浮层

最终改良代码

$(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. 事件对象同时也不是目标区域的子元素
*/
上一篇下一篇

猜你喜欢

热点阅读