业务 | 点击空白处关闭弹窗

935 阅读2分钟

工作中经常遇到一个业务场景:点击空白处(非弹窗内)时关闭弹窗。这里总结一下写法。

场景描述

  • 点击【添加】按钮弹窗出现
  • 再次点击【添加】按钮或其他非弹窗区域,弹窗消失

image.png

实现

如果只是某一个组件中用到这个功能,可以直接在组件中实现;不过为了提高复用性,可以将这个功能注册成vue的自定义指令。以下是这两种方法的具体实现

单组件中常规方法

思路

  • 弹窗出现时,全局注册事件监听;
  • 在注册事件内对比判断点击区域是否被弹窗元素包围,如果不满足条件,弹窗消失;
  • 在弹窗关闭时,移除事件监听。

代码

模板:

// template
<template>
    <div>
      <span class="add-btn" ref="addBtn" @click="handleClick">添加</span>
        // 添加弹窗是子组件的形式
       <add-panel
          v-if="showAddPanel"
          class="add-panel"
          ref="addPanel"
       ></add-panel>
    </div>   
</template>

逻辑:

<script>
data () {
    return {
        showAddPanel: false,
    }
},
methods: {
    handleClick () {
        // 这里是实现再次点击【添加】按钮关闭弹窗
        this.showAddPanel = !this.showAddPanel
        // 这里根据弹窗是否显示来绑定或解绑点击事件
        if (this.showAddPanel) {
            this.show()
        } else {
            this.hide()
        }
    },
    
    show () {
        document.addEventListener('click', this.hidePanel, false)
    },

    hide () {
        document.removeEventListener('click', this.hidePanel, false)
    },
    
    hidePanel (e) {
        // this.$refs.addPanel.$el.contains(e.target)是判断当前点击的区域是不是弹窗内
        // e.target !== this.$refs.addBtn 是判断当前点击的不是【添加】按钮,加这个条件是因为添加按钮区域也不属于弹窗内,如果不加这个条件,会立即触发this.showAddPanel = false,弹窗永远不会出现
        if (!this.$refs.addPanel.$el.contains(e.target) && e.target !== this.$refs.addBtn) {
            this.showAddPanel = false
            this.hide()
        }
    }
}
</script>

自定义指令

思路

  • 全局注册自定义指令v-closePanel
  • bind,即组件挂载时绑定点击事件;
  • unbind,即组件消失时解绑点击事件;
  • 注意组件的显示控制一定要用v-if指令,不能用v-show,这样能够触发钩子函数;

代码

  • 全局注册自定义指令v-closePanel

    • 新建directives文件夹
    • directives新建index.js
    import closePanel from './closePanel'
    
    const directives = {
      closePanel,
    }
    
    export default {
      install (Vue) {
        Object.keys(directives).forEach((key) => {
          Vue.directive(key, directives[key])
        })
      }
    }
    
    • 新建closePanel.js
    const closePanel = {
      // 指令第一次绑定元素时使用,在这里进行初始化
      bind (el, binding) {
        // el为指令绑定的元素,可以用来直接操作DOM
        // binding为一个对象,binding.name 指令名;binding.value 指令绑定的值;
        const documentClickFn = (e) => {
          if (el.contains(e.target)) {
            return false
          }
          // v-close="XX", binding.expression这里指的是XX,binding.value为XX具体的value
          if (binding.expression) {
            binding.value(e)
          }
        }
        // 把事件放到当前元素的私有变量里面,用于unbind时解绑
        el._vueDocumentClick = documentClickFn
        document.addEventListener('click', el._vueDocumentClick)
      },
    
      unbind (el) {
        document.removeEventListener('click', el._vueDocumentClick)
        delete el._vueDocumentClick
      }
    }
    
    export default closePanel
    
    
    • main.js中引入
    // main.js
    
    import Directives from 'common/directives'
    
    Vue.use(Directives)
    
  • 组件使用

<template>
    <div>
      <span class="add-btn" ref="addBtn" @click="handleClick">添加</span>
        // 添加弹窗是子组件的形式
       <add-panel
          v-if="showAddPanel"
          v-closePanel="close"
          class="add-panel"
       ></add-panel>
    </div>   
</template>

<script>
export default {
    ……
    data () {
        return {
            showAddPanel: false,
        }
    },
    
    methods: {
        handleClick () {
            this.showAddPanel = !this.showAddPanel
        },

        close (e) {
            if (!this.$refs.addBtn.contains(e.target)) {
                this.showAddPanel = false
            }
        }
    },
}
</script>

————

完成实现。欢迎大佬们指点,一起学习,一起进步。