工作中经常遇到一个业务场景:点击空白处(非弹窗内)时关闭弹窗。这里总结一下写法。
场景描述
- 点击【添加】按钮弹窗出现
- 再次点击【添加】按钮或其他非弹窗区域,弹窗消失
实现
如果只是某一个组件中用到这个功能,可以直接在组件中实现;不过为了提高复用性,可以将这个功能注册成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>
————
完成实现。欢迎大佬们指点,一起学习,一起进步。