[JavaScript]继续学习DOM事件模型

继续学习DOM事件模型

继续深入一下对于DOM事件模型的学习,理解事件捕获与冒泡。

假设我们有一个需求,点击之后浮层出现;点击别处浮层消失。

<!DOCTYPE html>
<html>
<head>
<script src="//code.jquery.com/jquery-1.9.1.min.js"></script>
  <meta charset="utf-8">
  <title>JS Bin</title>
</head>
<body>
   <div class='wrapper' id='wrapper'>
     <button id='clickMe'>点击</button>
     <div class="popover" id='popover'>浮层</div>
   </div>
</body>
</html>
.wrapper {
  position: relative;
  display: inline-block;
}
.popover {
  border:1px solid red;
  position: absolute;
  left:100%;
  top: 0;
  white-space: nowrap;
  padding: 10px;
  margin-left: 10px;
  background-color: #fff;
  display: none;
}
.popover::before {
  position: absolute;
  border:10px solid transparent;
  border-right-color:red;
  right:100%;
  top:5px;
  content:''
}
.popover::after {
  position: absolute;
  border:10px solid transparent;
  border-right-color:white;
  right:100%;
  top:5px;
  content:'';
  margin-right: -1px;
}

给按钮添加事件:

clickMe.addEventListener('click',function(){
  popover.style.display = 'block'
})

按钮点击之后,浮层出现。

如果要设置成点击别处之后浮层消失,该怎么操作?

思路1:监听body,当body被点击后,浮层消失

document.body.addEventListener('click',function(){
    console.log(1)
    popover.style.display = 'none'
})

点击图中的空白区域,完全没有响应:

点击后无反应

解决方法:给我们的body添加一个border,看看我们的点击区域是不是在body内:

body {border:1px solid red}

body所在的位置

果然,因为body的高度是由其内部文档流元素的总体高度决定的,所以body的高度如图所示。我们只有点击了红色框内的范围,点击事件才会响应。

所以监听body是有可能会因为body高度不够而达不到需要的效果的。

思路2:监听document

修改代码:

document.addEventListener('click',function(){
    console.log(1)
    popover.style.display = 'none'
})

监听document

可以看到无论点击哪里,1都会被打印出来。

但是问题又来了:我们的点击按钮失效了。

事件传播过程

我们知道,事件传播顺序是先执行捕获,再执行冒泡,如果addEventListener()没有指定第三个参数为true或者false,则默认为false,即表示事件的回调函数会在事件冒泡阶段执行。

在这个过程中,两个事件都没有指定第三个参数,即都为false。当监听队列从document捕获到button,他们并没有做出反应,因为他们的回调函数不是在事件捕获阶段执行的;当从button冒泡到document时,他们的回调函数才会被触发,所以是先执行button的回调函数,运行popover.style.display = 'block',再执行document的回调函数,执行popover.style.display = 'none',他的样式是先被显示,然后马上被隐藏,所以看起来像是点击按钮失效了。

事件传播

我们可以验证这一过程

clickMe.addEventListener('click',function(){
    console.log(2)
    popover.style.display = 'block'
})
document.addEventListener('click',function(){
    console.log(1)
    popover.style.display = 'none'
})

运行顺序

看到控制台先打印出2,再打印出1。

阻止传播

可以用阻止传播来解决这个问题

clickMe.addEventListener('click',function(e){
    popover.style.display = 'block'
    e.stopPropagation()
})

浮层就出来了:

阻止冒泡

没错,看似可以接受的方案,问题又来了,如果点击按钮后出现的这个浮层,它里面有内容,而我们又想选择里面的内容(比如一个CheckBox),但是这个浮层现在一点击就会消失,该怎么办呢?

让他的父元素阻止冒泡

wrapper.addEventListener('click',function(e){
    e.stopPropagation()
})

这样我们就可以点击浮层而不让他消失了。

优化

  • 阻止冒泡,并且只有当按钮被点击之后document才添加对点击事件的的一次监听,document对于其他点击事件不监听,节省内存。
$(clickMe).on('click',function(){
  $(popover).show()
  $(document).one('click',function(){
    $(popover).hide()
})
})
$(wrapper).on('click',function(e){
  e.stopPropagation()
})
  • 或者添加setTimeout(),等一会(冒泡过程结束后)再把document的click事件添加进去:
$(clickMe).on('click',function(){
  $(popover).show()
  console.log('show')
  setTimeout(function(){
      console.log('添加one click')
      $(document).one('click',function(){
          console.log('点击按钮后这里不会被执行,因为冒泡通知过程已经结束')
        $(popover).hide()
    })
  },0)
})

冒泡过程已结束

document回过头来处理

总结:

  • W3C制定了统一的标准-先捕获再冒泡

  • addEventListener有三个参数:element.addEventListener(event, function, useCapture)

    • 第一个参数是需要绑定的事件
    • 第二个参数是触发事件后要执行的函数
    • 第三个参数默认值是false,表示在事件冒泡阶段调用事件处理函数;如果参数为true,则表示在事件捕获阶段调用处理函数。
  • 传播过程可以用stopPorpagation()来阻止(但是要注意选择合适的节点使用)

本人文笔不好,关于事件的更详细的说明可以参考segmentfault上的这篇博文

猜你喜欢

转载自www.cnblogs.com/No-harm/p/9588411.html