写出一个点开浮层, 关闭浮层的例子, 要求:
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. 事件对象同时也不是目标区域的子元素
- */
来源: http://www.jianshu.com/p/2cccd0f3ab20